mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-21 14:38:43 +00:00
Compare commits
53 Commits
snapshot-f
...
snapshot-f
Author | SHA1 | Date | |
---|---|---|---|
21f08f3499 | |||
8f334c9a53 | |||
9d64c34d8b | |||
a52fd34e7d | |||
6225169527 | |||
9b4b61af38 | |||
f237b5e82d | |||
207311eb0b | |||
ab5af234af | |||
9baa7c32bf | |||
3eae3a5758 | |||
a11d584c8a | |||
ba3cf53c30 | |||
d44790545d | |||
5aed59b9b1 | |||
3bcc0a17bc | |||
09c4d99612 | |||
26fc4fb4e0 | |||
d92be4e69b | |||
2de3306f95 | |||
58b0936c50 | |||
583d702355 | |||
06a69279a8 | |||
9a2276edf2 | |||
fa2b1c0b8e | |||
840e1aada3 | |||
22d5bc547c | |||
b7cfea157d | |||
028751e23a | |||
be0e1f9c0b | |||
912e40fe0b | |||
f9af2ba983 | |||
61ec57af02 | |||
ccce1a3d1f | |||
eb36630bcd | |||
c3f702bb65 | |||
31fa3c3921 | |||
099fd27309 | |||
95998b687b | |||
1a6cc5bf3c | |||
27a6c0d53c | |||
27c693f0c4 | |||
a3433d60f7 | |||
c2a13b1ada | |||
576ca8a6df | |||
a9a988f221 | |||
7f23a0da79 | |||
0d2e6012a3 | |||
bce9af06fb | |||
385f4c20e5 | |||
6b7fb21d76 | |||
172b25f33e | |||
8dbf7fe4cd |
@ -7,6 +7,8 @@ fi
|
||||
|
||||
if [ "$CIRCLE_BRANCH" == "dev" ] || [ "$CIRCLE_BRANCH" == "staging" ] || [ "$CIRCLE_BRANCH" == "main" ] || [ \! -z $(echo "$CIRCLE_TAG" | grep "^version-") ]; then
|
||||
echo "On a primary branch or tag [${CIRCLE_BRANCH}${CIRCLE_TAG}] - will not edit the pom version.";
|
||||
echo "(or do we need to do this for dev...?)"
|
||||
echo "(maybe we do this if the current version is a SNAPSHOT?)"
|
||||
exit 0;
|
||||
fi
|
||||
|
||||
@ -17,7 +19,9 @@ else
|
||||
fi
|
||||
|
||||
POM=$(dirname $0)/../pom.xml
|
||||
UNIQ=$(date +%Y%m%d-%H%M%S)-$(git rev-parse --short HEAD)
|
||||
SLUG="${SLUG}-${UNIQ}"
|
||||
|
||||
echo "Updating $POM <revision> to: $SLUG-SNAPSHOT"
|
||||
sed -i "s/<revision>.*/<revision>$SLUG-SNAPSHOT<\/revision>/" $POM
|
||||
echo "Updating $POM <revision> to: $SLUG"
|
||||
sed -i "s/<revision>.*/<revision>$SLUG<\/revision>/" $POM
|
||||
git diff $POM
|
||||
|
@ -48,3 +48,5 @@ 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/>.
|
||||
|
||||
|
||||
|
@ -213,18 +213,6 @@
|
||||
<property name="tokens" value="VARIABLE_DEF"/>
|
||||
<property name="allowSamelineMultipleAnnotations" value="true"/>
|
||||
</module>
|
||||
<module name="NonEmptyAtclauseDescription"/>
|
||||
<!-- <module name="JavadocTagContinuationIndentation"/> -->
|
||||
<!--
|
||||
<module name="SummaryJavadoc">
|
||||
<property name="forbiddenSummaryFragments" value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/>
|
||||
</module>
|
||||
-->
|
||||
<!-- <module name="JavadocParagraph"/> -->
|
||||
<module name="AtclauseOrder">
|
||||
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
|
||||
<property name="target" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
|
||||
</module>
|
||||
<module name="JavadocMethod">
|
||||
<property name="allowMissingParamTags" value="true"/>
|
||||
<property name="allowMissingReturnTag" value="true"/>
|
||||
@ -233,23 +221,14 @@
|
||||
<module name="MissingJavadocMethod">
|
||||
<property name="scope" value="private"/>
|
||||
</module>
|
||||
<module name="MissingJavadocType">
|
||||
<property name="scope" value="private"/>
|
||||
</module>
|
||||
<module name="MethodName">
|
||||
<property name="format" value="^[a-z][a-zA-Z0-9_]*$"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Method name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="SingleLineJavadoc">
|
||||
<property name="ignoreInlineTags" value="false"/>
|
||||
</module>
|
||||
|
||||
<module name="MagicNumber">
|
||||
<property name="severity" value="info"/>
|
||||
<property name="tokens" value="NUM_DOUBLE, NUM_FLOAT, NUM_INT"/>
|
||||
<property name="ignoreNumbers" value="0, 1, 2, 3, 4, 5, 6, 7, 8"/>
|
||||
<property name="ignoreFieldDeclaration" value="true"/>
|
||||
<property name="ignoreAnnotation" value="true"/>
|
||||
</module>
|
||||
|
||||
<module name="EmptyCatchBlock">
|
||||
<property name="exceptionVariableName" value="expected"/>
|
||||
</module>
|
||||
|
4
pom.xml
4
pom.xml
@ -50,8 +50,7 @@
|
||||
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<maven.compiler.release>17</maven.compiler.release>
|
||||
<maven.compiler.showDeprecation>true</maven.compiler.showDeprecation>
|
||||
<maven.compiler.showWarnings>true</maven.compiler.showWarnings>
|
||||
<coverage.haltOnFailure>true</coverage.haltOnFailure>
|
||||
@ -168,6 +167,7 @@
|
||||
<violationSeverity>warning</violationSeverity>
|
||||
<excludes>**/target/generated-sources/*.*</excludes>
|
||||
<!-- <linkXRef>false</linkXRef> -->
|
||||
<includeTestSourceDirectory>true</includeTestSourceDirectory>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
|
@ -37,7 +37,7 @@ import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
**
|
||||
** Note: One would imagine that this class shouldn't ever implement Serializable...
|
||||
*******************************************************************************/
|
||||
public class QBackendTransaction
|
||||
public class QBackendTransaction implements AutoCloseable
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
|
@ -344,6 +344,9 @@ public class RecordAutomationStatusUpdater
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private record Key(QTableMetaData table, TriggerEvent triggerEvent) {}
|
||||
|
||||
}
|
||||
|
@ -55,10 +55,10 @@ public abstract class AbstractPreInsertCustomizer implements TableCustomizerInte
|
||||
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// allow the customizer to specify when it should be executed as part of the //
|
||||
// insert action. default (per method in this class) is AFTER_ALL_VALIDATIONS //
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
/***************************************************************************
|
||||
** allow the customizer to specify when it should be executed as part of the
|
||||
** insert action. default (per method in this class) is AFTER_ALL_VALIDATIONS
|
||||
***************************************************************************/
|
||||
public enum WhenToRun
|
||||
{
|
||||
BEFORE_ALL_VALIDATIONS,
|
||||
|
@ -28,6 +28,7 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
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.exceptions.QRuntimeException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
@ -49,6 +50,9 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
*******************************************************************************/
|
||||
public abstract class ChildInserterPostInsertCustomizer extends AbstractPostInsertCustomizer
|
||||
{
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public enum RelationshipType
|
||||
{
|
||||
PARENT_POINTS_AT_CHILD,
|
||||
@ -97,7 +101,7 @@ public abstract class ChildInserterPostInsertCustomizer extends AbstractPostInse
|
||||
List<QRecord> rs = records;
|
||||
List<QRecord> childrenToInsert = new ArrayList<>();
|
||||
QTableMetaData table = getInsertInput().getTable();
|
||||
QTableMetaData childTable = getInsertInput().getInstance().getTable(getChildTableName());
|
||||
QTableMetaData childTable = QContext.getQInstance().getTable(getChildTableName());
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// iterate over the inserted records, building a list child records to insert //
|
||||
|
@ -31,6 +31,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.SearchPossibleValueSourceAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
@ -102,7 +103,7 @@ public abstract class AbstractWidgetRenderer
|
||||
String possibleValueSourceName = dropdownData.getPossibleValueSourceName();
|
||||
if(possibleValueSourceName != null)
|
||||
{
|
||||
QPossibleValueSource possibleValueSource = input.getInstance().getPossibleValueSource(possibleValueSourceName);
|
||||
QPossibleValueSource possibleValueSource = QContext.getQInstance().getPossibleValueSource(possibleValueSourceName);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// this looks complicated, but is just look for a label in the dropdown data and if found use it, //
|
||||
|
@ -34,6 +34,7 @@ import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
@ -181,10 +182,10 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
||||
{
|
||||
String widgetLabel = input.getQueryParams().get("widgetLabel");
|
||||
String joinName = input.getQueryParams().get("joinName");
|
||||
QJoinMetaData join = input.getInstance().getJoin(joinName);
|
||||
QJoinMetaData join = QContext.getQInstance().getJoin(joinName);
|
||||
String id = input.getQueryParams().get("id");
|
||||
QTableMetaData leftTable = input.getInstance().getTable(join.getLeftTable());
|
||||
QTableMetaData rightTable = input.getInstance().getTable(join.getRightTable());
|
||||
QTableMetaData leftTable = QContext.getQInstance().getTable(join.getLeftTable());
|
||||
QTableMetaData rightTable = QContext.getQInstance().getTable(join.getRightTable());
|
||||
|
||||
Integer maxRows = null;
|
||||
if(StringUtils.hasContent(input.getQueryParams().get("maxRows")))
|
||||
@ -252,7 +253,7 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
||||
}
|
||||
}
|
||||
|
||||
String tablePath = input.getInstance().getTablePath(rightTable.getName());
|
||||
String tablePath = QContext.getQInstance().getTablePath(rightTable.getName());
|
||||
String viewAllLink = tablePath == null ? null : (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset()));
|
||||
|
||||
ChildRecordListData widgetData = new ChildRecordListData(widgetLabel, queryOutput, rightTable, tablePath, viewAllLink, totalRows);
|
||||
@ -278,7 +279,9 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
||||
Map<String, Serializable> widgetValues = input.getWidgetMetaData().getDefaultValues();
|
||||
if(widgetValues.containsKey("disabledFieldsForNewChildRecords"))
|
||||
{
|
||||
widgetData.setDisabledFieldsForNewChildRecords((Set<String>) widgetValues.get("disabledFieldsForNewChildRecords"));
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<String> disabledFieldsForNewChildRecords = (Set<String>) widgetValues.get("disabledFieldsForNewChildRecords");
|
||||
widgetData.setDisabledFieldsForNewChildRecords(disabledFieldsForNewChildRecords);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.actions.dashboard.widgets;
|
||||
|
||||
import java.util.HashMap;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput;
|
||||
@ -57,7 +58,7 @@ public class ProcessWidgetRenderer extends AbstractWidgetRenderer
|
||||
setupDropdowns(input, widgetMetaData, data);
|
||||
|
||||
String processName = (String) widgetMetaData.getDefaultValues().get(WIDGET_PROCESS_NAME);
|
||||
QProcessMetaData processMetaData = input.getInstance().getProcess(processName);
|
||||
QProcessMetaData processMetaData = QContext.getQInstance().getProcess(processName);
|
||||
data.setProcessMetaData(processMetaData);
|
||||
|
||||
data.setDefaultValues(new HashMap<>(input.getQueryParams()));
|
||||
|
@ -30,6 +30,7 @@ import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionCheckResult;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
|
||||
@ -72,7 +73,7 @@ public class MetaDataAction
|
||||
// map tables to frontend metadata //
|
||||
/////////////////////////////////////
|
||||
Map<String, QFrontendTableMetaData> tables = new LinkedHashMap<>();
|
||||
for(Map.Entry<String, QTableMetaData> entry : metaDataInput.getInstance().getTables().entrySet())
|
||||
for(Map.Entry<String, QTableMetaData> entry : QContext.getQInstance().getTables().entrySet())
|
||||
{
|
||||
String tableName = entry.getKey();
|
||||
QTableMetaData table = entry.getValue();
|
||||
@ -83,7 +84,7 @@ public class MetaDataAction
|
||||
continue;
|
||||
}
|
||||
|
||||
QBackendMetaData backendForTable = metaDataInput.getInstance().getBackendForTable(tableName);
|
||||
QBackendMetaData backendForTable = QContext.getQInstance().getBackendForTable(tableName);
|
||||
tables.put(tableName, new QFrontendTableMetaData(metaDataInput, backendForTable, table, false, false));
|
||||
treeNodes.put(tableName, new AppTreeNode(table));
|
||||
}
|
||||
@ -96,7 +97,7 @@ public class MetaDataAction
|
||||
// map processes to frontend metadata //
|
||||
////////////////////////////////////////
|
||||
Map<String, QFrontendProcessMetaData> processes = new LinkedHashMap<>();
|
||||
for(Map.Entry<String, QProcessMetaData> entry : metaDataInput.getInstance().getProcesses().entrySet())
|
||||
for(Map.Entry<String, QProcessMetaData> entry : QContext.getQInstance().getProcesses().entrySet())
|
||||
{
|
||||
String processName = entry.getKey();
|
||||
QProcessMetaData process = entry.getValue();
|
||||
@ -116,7 +117,7 @@ public class MetaDataAction
|
||||
// map reports to frontend metadata //
|
||||
//////////////////////////////////////
|
||||
Map<String, QFrontendReportMetaData> reports = new LinkedHashMap<>();
|
||||
for(Map.Entry<String, QReportMetaData> entry : metaDataInput.getInstance().getReports().entrySet())
|
||||
for(Map.Entry<String, QReportMetaData> entry : QContext.getQInstance().getReports().entrySet())
|
||||
{
|
||||
String reportName = entry.getKey();
|
||||
QReportMetaData report = entry.getValue();
|
||||
@ -136,7 +137,7 @@ public class MetaDataAction
|
||||
// map widgets to frontend metadata //
|
||||
//////////////////////////////////////
|
||||
Map<String, QFrontendWidgetMetaData> widgets = new LinkedHashMap<>();
|
||||
for(Map.Entry<String, QWidgetMetaDataInterface> entry : metaDataInput.getInstance().getWidgets().entrySet())
|
||||
for(Map.Entry<String, QWidgetMetaDataInterface> entry : QContext.getQInstance().getWidgets().entrySet())
|
||||
{
|
||||
String widgetName = entry.getKey();
|
||||
QWidgetMetaDataInterface widget = entry.getValue();
|
||||
@ -154,7 +155,7 @@ public class MetaDataAction
|
||||
///////////////////////////////////////////////////////
|
||||
// sort apps - by sortOrder (integer), then by label //
|
||||
///////////////////////////////////////////////////////
|
||||
List<QAppMetaData> sortedApps = metaDataInput.getInstance().getApps().values().stream()
|
||||
List<QAppMetaData> sortedApps = QContext.getQInstance().getApps().values().stream()
|
||||
.sorted(Comparator.comparing((QAppMetaData a) -> a.getSortOrder())
|
||||
.thenComparing((QAppMetaData a) -> a.getLabel()))
|
||||
.toList();
|
||||
@ -211,14 +212,14 @@ public class MetaDataAction
|
||||
////////////////////////////////////
|
||||
// add branding metadata if found //
|
||||
////////////////////////////////////
|
||||
if(metaDataInput.getInstance().getBranding() != null)
|
||||
if(QContext.getQInstance().getBranding() != null)
|
||||
{
|
||||
metaDataOutput.setBranding(metaDataInput.getInstance().getBranding());
|
||||
metaDataOutput.setBranding(QContext.getQInstance().getBranding());
|
||||
}
|
||||
|
||||
metaDataOutput.setEnvironmentValues(metaDataInput.getInstance().getEnvironmentValues());
|
||||
metaDataOutput.setEnvironmentValues(QContext.getQInstance().getEnvironmentValues());
|
||||
|
||||
metaDataOutput.setHelpContents(metaDataInput.getInstance().getHelpContent());
|
||||
metaDataOutput.setHelpContents(QContext.getQInstance().getHelpContent());
|
||||
|
||||
// todo post-customization - can do whatever w/ the result if you want?
|
||||
|
||||
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.actions.metadata;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.ProcessMetaDataInput;
|
||||
@ -47,7 +48,7 @@ public class ProcessMetaDataAction
|
||||
// todo pre-customization - just get to modify the request?
|
||||
ProcessMetaDataOutput processMetaDataOutput = new ProcessMetaDataOutput();
|
||||
|
||||
QProcessMetaData process = processMetaDataInput.getInstance().getProcess(processMetaDataInput.getProcessName());
|
||||
QProcessMetaData process = QContext.getQInstance().getProcess(processMetaDataInput.getProcessName());
|
||||
if(process == null)
|
||||
{
|
||||
throw (new QNotFoundException("Process [" + processMetaDataInput.getProcessName() + "] was not found."));
|
||||
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.actions.metadata;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataInput;
|
||||
@ -48,12 +49,12 @@ public class TableMetaDataAction
|
||||
// todo pre-customization - just get to modify the request?
|
||||
TableMetaDataOutput tableMetaDataOutput = new TableMetaDataOutput();
|
||||
|
||||
QTableMetaData table = tableMetaDataInput.getInstance().getTable(tableMetaDataInput.getTableName());
|
||||
QTableMetaData table = QContext.getQInstance().getTable(tableMetaDataInput.getTableName());
|
||||
if(table == null)
|
||||
{
|
||||
throw (new QNotFoundException("Table [" + tableMetaDataInput.getTableName() + "] was not found."));
|
||||
}
|
||||
QBackendMetaData backendForTable = tableMetaDataInput.getInstance().getBackendForTable(table.getName());
|
||||
QBackendMetaData backendForTable = QContext.getQInstance().getBackendForTable(table.getName());
|
||||
tableMetaDataOutput.setTable(new QFrontendTableMetaData(tableMetaDataInput, backendForTable, table, true, true));
|
||||
|
||||
// todo post-customization - can do whatever w/ the result if you want
|
||||
|
@ -49,6 +49,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -78,6 +79,12 @@ public class PermissionsHelper
|
||||
warnAboutPermissionSubTypeForTables(permissionSubType);
|
||||
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||
|
||||
if(table == null)
|
||||
{
|
||||
LOG.info("Throwing a permission denied exception in response to a non-existent table name", logPair("tableName", tableName));
|
||||
throw (new QPermissionDeniedException("Permission denied."));
|
||||
}
|
||||
|
||||
commonCheckPermissionThrowing(getEffectivePermissionRules(table, QContext.getQInstance()), permissionSubType, table.getName());
|
||||
}
|
||||
|
||||
@ -184,7 +191,14 @@ public class PermissionsHelper
|
||||
*******************************************************************************/
|
||||
public static void checkProcessPermissionThrowing(AbstractActionInput actionInput, String processName, Map<String, Serializable> processValues) throws QPermissionDeniedException
|
||||
{
|
||||
QProcessMetaData process = QContext.getQInstance().getProcess(processName);
|
||||
QProcessMetaData process = QContext.getQInstance().getProcess(processName);
|
||||
|
||||
if(process == null)
|
||||
{
|
||||
LOG.info("Throwing a permission denied exception in response to a non-existent process name", logPair("processName", processName));
|
||||
throw (new QPermissionDeniedException("Permission denied."));
|
||||
}
|
||||
|
||||
QPermissionRules effectivePermissionRules = getEffectivePermissionRules(process, QContext.getQInstance());
|
||||
|
||||
if(effectivePermissionRules.getCustomPermissionChecker() != null)
|
||||
@ -226,6 +240,13 @@ public class PermissionsHelper
|
||||
public static void checkAppPermissionThrowing(AbstractActionInput actionInput, String appName) throws QPermissionDeniedException
|
||||
{
|
||||
QAppMetaData app = QContext.getQInstance().getApp(appName);
|
||||
|
||||
if(app == null)
|
||||
{
|
||||
LOG.info("Throwing a permission denied exception in response to a non-existent app name", logPair("appName", appName));
|
||||
throw (new QPermissionDeniedException("Permission denied."));
|
||||
}
|
||||
|
||||
commonCheckPermissionThrowing(getEffectivePermissionRules(app, QContext.getQInstance()), PrivatePermissionSubType.HAS_ACCESS, app.getName());
|
||||
}
|
||||
|
||||
@ -255,6 +276,13 @@ public class PermissionsHelper
|
||||
public static void checkReportPermissionThrowing(AbstractActionInput actionInput, String reportName) throws QPermissionDeniedException
|
||||
{
|
||||
QReportMetaData report = QContext.getQInstance().getReport(reportName);
|
||||
|
||||
if(report == null)
|
||||
{
|
||||
LOG.info("Throwing a permission denied exception in response to a non-existent process name", logPair("reportName", reportName));
|
||||
throw (new QPermissionDeniedException("Permission denied."));
|
||||
}
|
||||
|
||||
commonCheckPermissionThrowing(getEffectivePermissionRules(report, QContext.getQInstance()), PrivatePermissionSubType.HAS_ACCESS, report.getName());
|
||||
}
|
||||
|
||||
@ -284,6 +312,13 @@ public class PermissionsHelper
|
||||
public static void checkWidgetPermissionThrowing(AbstractActionInput actionInput, String widgetName) throws QPermissionDeniedException
|
||||
{
|
||||
QWidgetMetaDataInterface widget = QContext.getQInstance().getWidget(widgetName);
|
||||
|
||||
if(widget == null)
|
||||
{
|
||||
LOG.info("Throwing a permission denied exception in response to a non-existent widget name", logPair("widgetName", widgetName));
|
||||
throw (new QPermissionDeniedException("Permission denied."));
|
||||
}
|
||||
|
||||
commonCheckPermissionThrowing(getEffectivePermissionRules(widget, QContext.getQInstance()), PrivatePermissionSubType.HAS_ACCESS, widget.getName());
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.actions.processes;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QBadRequestException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
@ -54,7 +55,7 @@ public class CancelProcessAction extends RunProcessAction
|
||||
{
|
||||
ActionHelper.validateSession(runProcessInput);
|
||||
|
||||
QProcessMetaData process = runProcessInput.getInstance().getProcess(runProcessInput.getProcessName());
|
||||
QProcessMetaData process = QContext.getQInstance().getProcess(runProcessInput.getProcessName());
|
||||
if(process == null)
|
||||
{
|
||||
throw new QBadRequestException("Process [" + runProcessInput.getProcessName() + "] is not defined in this instance.");
|
||||
|
@ -30,6 +30,7 @@ import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
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.exceptions.QUserFacingException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
@ -64,7 +65,7 @@ public class RunBackendStepAction
|
||||
{
|
||||
ActionHelper.validateSession(runBackendStepInput);
|
||||
|
||||
QProcessMetaData process = runBackendStepInput.getInstance().getProcess(runBackendStepInput.getProcessName());
|
||||
QProcessMetaData process = QContext.getQInstance().getProcess(runBackendStepInput.getProcessName());
|
||||
if(process == null)
|
||||
{
|
||||
throw new QException("Process [" + runBackendStepInput.getProcessName() + "] is not defined in this instance.");
|
||||
|
@ -99,7 +99,7 @@ public class RunProcessAction
|
||||
{
|
||||
ActionHelper.validateSession(runProcessInput);
|
||||
|
||||
QProcessMetaData process = runProcessInput.getInstance().getProcess(runProcessInput.getProcessName());
|
||||
QProcessMetaData process = QContext.getQInstance().getProcess(runProcessInput.getProcessName());
|
||||
if(process == null)
|
||||
{
|
||||
throw new QException("Process [" + runProcessInput.getProcessName() + "] is not defined in this instance.");
|
||||
|
@ -0,0 +1,202 @@
|
||||
/*
|
||||
* 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.reporting;
|
||||
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportDestination;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin;
|
||||
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.Pair;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility for verifying that the ExportAction works for all tables, and all
|
||||
** exposed joins.
|
||||
**
|
||||
** Meant for use within a unit test, or maybe as part of an instance's boot-up/
|
||||
** validation.
|
||||
*******************************************************************************/
|
||||
public class ExportsFullInstanceVerifier
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(ExportsFullInstanceVerifier.class);
|
||||
|
||||
private boolean filterForAtMostOneRowPerExport = true;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void verify(Collection<QTableMetaData> tables) throws QException
|
||||
{
|
||||
Map<Pair<String, String>, Exception> caughtExceptions = new LinkedHashMap<>();
|
||||
for(QTableMetaData table : tables)
|
||||
{
|
||||
if(table.isCapabilityEnabled(QContext.getQInstance().getBackendForTable(table.getName()), Capability.TABLE_QUERY))
|
||||
{
|
||||
LOG.info("Verifying Exports on table", logPair("tableName", table.getName()));
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// run the table by itself (no join fields) //
|
||||
//////////////////////////////////////////////
|
||||
runExport(table.getName(), Collections.emptyList(), "main-table-only", caughtExceptions);
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
// run once w/ the fields from each exposed join //
|
||||
///////////////////////////////////////////////////
|
||||
for(ExposedJoin exposedJoin : CollectionUtils.nonNullList(table.getExposedJoins()))
|
||||
{
|
||||
runExport(table.getName(), List.of(exposedJoin), "join-" + exposedJoin.getLabel(), caughtExceptions);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// run w/ all exposed joins (if there are any) //
|
||||
/////////////////////////////////////////////////
|
||||
if(CollectionUtils.nullSafeHasContents(table.getExposedJoins()))
|
||||
{
|
||||
runExport(table.getName(), table.getExposedJoins(), "all-joins", caughtExceptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// log out an exceptions caught //
|
||||
//////////////////////////////////
|
||||
if(!caughtExceptions.isEmpty())
|
||||
{
|
||||
for(Map.Entry<Pair<String, String>, Exception> entry : caughtExceptions.entrySet())
|
||||
{
|
||||
LOG.info("Caught an exception verifying reports", entry.getValue(), logPair("tableName", entry.getKey().getA()), logPair("fieldName", entry.getKey().getB()));
|
||||
}
|
||||
throw (new QException("Reports Verification failed with " + caughtExceptions.size() + " exception" + StringUtils.plural(caughtExceptions.size())));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void runExport(String tableName, List<ExposedJoin> exposedJoinList, String description, Map<Pair<String, String>, Exception> caughtExceptions)
|
||||
{
|
||||
try
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// build the list of fieldNames to export - starting with all fields in the table //
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
List<String> fieldNames = new ArrayList<>();
|
||||
for(QFieldMetaData field : QContext.getQInstance().getTable(tableName).getFields().values())
|
||||
{
|
||||
fieldNames.add(field.getName());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
// add all fields from all exposed joins as well //
|
||||
///////////////////////////////////////////////////
|
||||
for(ExposedJoin exposedJoin : CollectionUtils.nonNullList(exposedJoinList))
|
||||
{
|
||||
QTableMetaData joinTable = QContext.getQInstance().getTable(exposedJoin.getJoinTable());
|
||||
for(QFieldMetaData field : joinTable.getFields().values())
|
||||
{
|
||||
fieldNames.add(joinTable.getName() + "." + field.getName());
|
||||
}
|
||||
}
|
||||
|
||||
LOG.info("Verifying export", logPair("description", description), logPair("fieldCount", fieldNames.size()));
|
||||
|
||||
QQueryFilter queryFilter = new QQueryFilter();
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if caller is okay with a filter that should limit the report to a small number of rows (could be more than 1 for to-many joins), then do so //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(filterForAtMostOneRowPerExport)
|
||||
{
|
||||
queryFilter.withCriteria(QContext.getQInstance().getTable(tableName).getPrimaryKeyField(), QCriteriaOperator.EQUALS, 1);
|
||||
}
|
||||
|
||||
ExportInput exportInput = new ExportInput();
|
||||
exportInput.setTableName(tableName);
|
||||
exportInput.setFieldNames(fieldNames);
|
||||
exportInput.setReportDestination(new ReportDestination()
|
||||
.withReportOutputStream(new ByteArrayOutputStream())
|
||||
.withReportFormat(ReportFormat.CSV));
|
||||
exportInput.setQueryFilter(queryFilter);
|
||||
new ExportAction().execute(exportInput);
|
||||
}
|
||||
catch(QException e)
|
||||
{
|
||||
caughtExceptions.put(Pair.of(tableName, description), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for filterForAtMostOneRowPerExport
|
||||
*******************************************************************************/
|
||||
public boolean getFilterForAtMostOneRowPerExport()
|
||||
{
|
||||
return (this.filterForAtMostOneRowPerExport);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for filterForAtMostOneRowPerExport
|
||||
*******************************************************************************/
|
||||
public void setFilterForAtMostOneRowPerExport(boolean filterForAtMostOneRowPerExport)
|
||||
{
|
||||
this.filterForAtMostOneRowPerExport = filterForAtMostOneRowPerExport;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for filterForAtMostOneRowPerExport
|
||||
*******************************************************************************/
|
||||
public ExportsFullInstanceVerifier withFilterForAtMostOneRowPerExport(boolean filterForAtMostOneRowPerExport)
|
||||
{
|
||||
this.filterForAtMostOneRowPerExport = filterForAtMostOneRowPerExport;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -66,6 +66,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
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;
|
||||
@ -303,7 +304,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
{
|
||||
if(StringUtils.hasContent(dataSource.getSourceTable()))
|
||||
{
|
||||
joinsContext = new JoinsContext(exportInput.getInstance(), dataSource.getSourceTable(), dataSource.getQueryJoins(), dataSource.getQueryFilter());
|
||||
joinsContext = new JoinsContext(QContext.getQInstance(), dataSource.getSourceTable(), cloneDataSourceQueryJoins(dataSource), dataSource.getQueryFilter() == null ? null : dataSource.getQueryFilter().clone());
|
||||
countDataSourceRecords(reportInput, dataSource, reportFormat);
|
||||
}
|
||||
}
|
||||
@ -351,7 +352,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
CountInput countInput = new CountInput();
|
||||
countInput.setTableName(dataSource.getSourceTable());
|
||||
countInput.setFilter(queryFilter);
|
||||
countInput.setQueryJoins(dataSource.getQueryJoins());
|
||||
countInput.setQueryJoins(cloneDataSourceQueryJoins(dataSource));
|
||||
CountOutput countOutput = new CountAction().execute(countInput);
|
||||
|
||||
if(countOutput.getCount() != null)
|
||||
@ -369,6 +370,26 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static List<QueryJoin> cloneDataSourceQueryJoins(QReportDataSource dataSource)
|
||||
{
|
||||
if(dataSource == null || dataSource.getQueryJoins() == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
List<QueryJoin> rs = new ArrayList<>();
|
||||
for(QueryJoin queryJoin : dataSource.getQueryJoins())
|
||||
{
|
||||
rs.add(queryJoin.clone());
|
||||
}
|
||||
return (rs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -417,12 +438,12 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
queryInput.setRecordPipe(recordPipe);
|
||||
queryInput.setTableName(dataSource.getSourceTable());
|
||||
queryInput.setFilter(queryFilter);
|
||||
queryInput.setQueryJoins(dataSource.getQueryJoins());
|
||||
queryInput.setQueryJoins(cloneDataSourceQueryJoins(dataSource));
|
||||
queryInput.withQueryHint(QueryHint.POTENTIALLY_LARGE_NUMBER_OF_RESULTS);
|
||||
queryInput.withQueryHint(QueryHint.MAY_USE_READ_ONLY_BACKEND);
|
||||
|
||||
queryInput.setShouldTranslatePossibleValues(true);
|
||||
queryInput.setFieldsToTranslatePossibleValues(setupFieldsToTranslatePossibleValues(reportInput, dataSource, new JoinsContext(reportInput.getInstance(), dataSource.getSourceTable(), dataSource.getQueryJoins(), queryInput.getFilter())));
|
||||
queryInput.setFieldsToTranslatePossibleValues(setupFieldsToTranslatePossibleValues(reportInput, dataSource));
|
||||
|
||||
if(dataSource.getQueryInputCustomizer() != null)
|
||||
{
|
||||
@ -474,7 +495,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
}
|
||||
consumedCount.getAndAdd(records.size());
|
||||
|
||||
return (consumeRecords(reportInput, dataSource, records, tableView, summaryViews, variantViews));
|
||||
return (consumeRecords(dataSource, records, tableView, summaryViews, variantViews));
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////
|
||||
@ -493,7 +514,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Set<String> setupFieldsToTranslatePossibleValues(ReportInput reportInput, QReportDataSource dataSource, JoinsContext joinsContext) throws QException
|
||||
private Set<String> setupFieldsToTranslatePossibleValues(ReportInput reportInput, QReportDataSource dataSource) throws QException
|
||||
{
|
||||
Set<String> fieldsToTranslatePossibleValues = new HashSet<>();
|
||||
|
||||
@ -574,9 +595,9 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Integer consumeRecords(ReportInput reportInput, QReportDataSource dataSource, List<QRecord> records, QReportView tableView, List<QReportView> summaryViews, List<QReportView> variantViews) throws QException
|
||||
private Integer consumeRecords(QReportDataSource dataSource, List<QRecord> records, QReportView tableView, List<QReportView> summaryViews, List<QReportView> variantViews) throws QException
|
||||
{
|
||||
QTableMetaData table = reportInput.getInstance().getTable(dataSource.getSourceTable());
|
||||
QTableMetaData table = QContext.getQInstance().getTable(dataSource.getSourceTable());
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// if this record goes on a table view, add it to the report streamer now //
|
||||
@ -687,7 +708,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void addRecordToSummaryKeyAggregates(QTableMetaData table, QRecord record, Map<SummaryKey, Map<String, AggregatesInterface<?, ?>>> viewAggregates, SummaryKey key) throws QException
|
||||
private void addRecordToSummaryKeyAggregates(QTableMetaData table, QRecord record, Map<SummaryKey, Map<String, AggregatesInterface<?, ?>>> viewAggregates, SummaryKey key)
|
||||
{
|
||||
Map<String, AggregatesInterface<?, ?>> keyAggregates = viewAggregates.computeIfAbsent(key, (name) -> new HashMap<>());
|
||||
addRecordToAggregatesMap(table, record, keyAggregates);
|
||||
@ -698,7 +719,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void addRecordToAggregatesMap(QTableMetaData table, QRecord record, Map<String, AggregatesInterface<?, ?>> aggregatesMap) throws QException
|
||||
private void addRecordToAggregatesMap(QTableMetaData table, QRecord record, Map<String, AggregatesInterface<?, ?>> aggregatesMap)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// todo - an optimization could be, to only compute aggregates that we'll need... //
|
||||
@ -706,7 +727,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
for(String fieldName : record.getValues().keySet())
|
||||
{
|
||||
QFieldMetaData field = null;
|
||||
QFieldMetaData field;
|
||||
try
|
||||
{
|
||||
//////////////////////////////////////////////////////
|
||||
@ -779,9 +800,14 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
List<QReportView> reportViews = views.stream().filter(v -> v.getType().equals(ReportType.SUMMARY)).toList();
|
||||
for(QReportView view : reportViews)
|
||||
{
|
||||
QReportDataSource dataSource = getDataSource(view.getDataSourceName());
|
||||
QTableMetaData table = reportInput.getInstance().getTable(dataSource.getSourceTable());
|
||||
SummaryOutput summaryOutput = computeSummaryRowsForView(reportInput, view, table);
|
||||
QReportDataSource dataSource = getDataSource(view.getDataSourceName());
|
||||
if(dataSource == null)
|
||||
{
|
||||
throw new QReportingException("Data source for summary view was not found (viewName=" + view.getName() + ", dataSourceName=" + view.getDataSourceName() + ").");
|
||||
}
|
||||
|
||||
QTableMetaData table = QContext.getQInstance().getTable(dataSource.getSourceTable());
|
||||
SummaryOutput summaryOutput = computeSummaryRowsForView(reportInput, view, table);
|
||||
|
||||
ExportInput exportInput = new ExportInput();
|
||||
exportInput.setReportDestination(reportInput.getReportDestination());
|
||||
@ -867,9 +893,8 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private SummaryOutput computeSummaryRowsForView(ReportInput reportInput, QReportView view, QTableMetaData table) throws QReportingException, QFormulaException
|
||||
private SummaryOutput computeSummaryRowsForView(ReportInput reportInput, QReportView view, QTableMetaData table) throws QFormulaException
|
||||
{
|
||||
QValueFormatter valueFormatter = new QValueFormatter();
|
||||
QMetaDataVariableInterpreter variableInterpreter = new QMetaDataVariableInterpreter();
|
||||
variableInterpreter.addValueMap("input", reportInput.getInputValues());
|
||||
variableInterpreter.addValueMap("total", getSummaryValuesForInterpreter(totalAggregates));
|
||||
@ -941,10 +966,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
if(CollectionUtils.nullSafeHasContents(view.getOrderByFields()))
|
||||
{
|
||||
summaryRows.sort((o1, o2) ->
|
||||
{
|
||||
return summaryRowComparator(view, o1, o2);
|
||||
});
|
||||
summaryRows.sort((o1, o2) -> summaryRowComparator(view, o1, o2));
|
||||
}
|
||||
|
||||
////////////////
|
||||
@ -979,8 +1001,6 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
Serializable serializable = getValueForColumn(variableInterpreter, column);
|
||||
totalRow.setValue(column.getName(), serializable);
|
||||
thisRowValues.put(column.getName(), serializable);
|
||||
|
||||
String formatted = valueFormatter.formatValue(column.getDisplayFormat(), serializable);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1003,7 +1023,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
titleValues.add(variableInterpreter.interpret(titleField));
|
||||
}
|
||||
|
||||
title = new QValueFormatter().formatStringWithValues(view.getTitleFormat(), titleValues);
|
||||
title = QValueFormatter.formatStringWithValues(view.getTitleFormat(), titleValues);
|
||||
}
|
||||
else if(StringUtils.hasContent(view.getTitleFormat()))
|
||||
{
|
||||
|
@ -124,7 +124,7 @@ public class ExcelFastexcelExportStreamer implements ExportStreamerInterface
|
||||
if(workbook == null)
|
||||
{
|
||||
String appName = ObjectUtils.tryAndRequireNonNullElse(() -> QContext.getQInstance().getBranding().getAppName(), "QQQ");
|
||||
QInstance instance = exportInput.getInstance();
|
||||
QInstance instance = QContext.getQInstance();
|
||||
if(instance != null && instance.getBranding() != null && instance.getBranding().getCompanyName() != null)
|
||||
{
|
||||
appName = instance.getBranding().getCompanyName();
|
||||
|
@ -161,7 +161,7 @@ public class StreamedSheetWriter
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Integer> m = new HashMap();
|
||||
Map<String, Integer> m = new HashMap<>();
|
||||
m.computeIfAbsent("s", (s) -> 3);
|
||||
|
||||
value = rs.toString();
|
||||
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.actions.scripts;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.scripts.logging.BuildScriptLogAndScriptLogLineExecutionLogger;
|
||||
@ -97,7 +98,7 @@ public class RecordScriptTestInterface implements TestScriptActionInterface
|
||||
}
|
||||
|
||||
QueryOutput queryOutput = new QueryAction().execute(new QueryInput(tableName)
|
||||
.withFilter(new QQueryFilter(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, recordPrimaryKeyList.split(","))))
|
||||
.withFilter(new QQueryFilter(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, Arrays.stream(recordPrimaryKeyList.split(",")).toList())))
|
||||
.withIncludeAssociations(true));
|
||||
if(CollectionUtils.nullSafeIsEmpty(queryOutput.getRecords()))
|
||||
{
|
||||
|
@ -154,8 +154,9 @@ public class RunAdHocRecordScriptAction
|
||||
Method qRecordListToApiRecordList = apiScriptUtilsClass.getMethod("qRecordListToApiRecordList", List.class, String.class, String.class, String.class);
|
||||
Object apiRecordList = qRecordListToApiRecordList.invoke(null, input.getRecordList(), input.getTableName(), scriptRevision.getApiName(), scriptRevision.getApiVersion());
|
||||
|
||||
// noinspection unchecked
|
||||
return (ArrayList<? extends Serializable>) apiRecordList;
|
||||
@SuppressWarnings("unchecked")
|
||||
ArrayList<? extends Serializable> rs = (ArrayList<? extends Serializable>) apiRecordList;
|
||||
return rs;
|
||||
}
|
||||
catch(ClassNotFoundException e)
|
||||
{
|
||||
|
@ -352,7 +352,7 @@ public class GetAction
|
||||
{
|
||||
if(qPossibleValueTranslator == null)
|
||||
{
|
||||
qPossibleValueTranslator = new QPossibleValueTranslator(getInput.getInstance(), getInput.getSession());
|
||||
qPossibleValueTranslator = new QPossibleValueTranslator(QContext.getQInstance(), QContext.getQSession());
|
||||
}
|
||||
qPossibleValueTranslator.translatePossibleValuesInRecords(getInput.getTable(), List.of(returnRecord));
|
||||
}
|
||||
|
@ -227,6 +227,11 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
*******************************************************************************/
|
||||
public void performValidations(InsertInput insertInput, boolean isPreview) throws QException
|
||||
{
|
||||
if(CollectionUtils.nullSafeIsEmpty(insertInput.getRecords()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QTableMetaData table = insertInput.getTable();
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
@ -241,7 +246,7 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
|
||||
setDefaultValuesInRecords(table, insertInput.getRecords());
|
||||
|
||||
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.INSERT, insertInput.getInstance(), table, insertInput.getRecords(), null);
|
||||
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.INSERT, QContext.getQInstance(), table, insertInput.getRecords(), null);
|
||||
|
||||
runPreInsertCustomizerIfItIsTime(insertInput, isPreview, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_UNIQUE_KEY_CHECKS);
|
||||
setErrorsIfUniqueKeyErrors(insertInput, table);
|
||||
|
@ -297,7 +297,7 @@ public class QueryAction
|
||||
{
|
||||
if(qPossibleValueTranslator == null)
|
||||
{
|
||||
qPossibleValueTranslator = new QPossibleValueTranslator(queryInput.getInstance(), queryInput.getSession());
|
||||
qPossibleValueTranslator = new QPossibleValueTranslator(QContext.getQInstance(), QContext.getQSession());
|
||||
}
|
||||
qPossibleValueTranslator.translatePossibleValuesInRecords(queryInput.getTable(), records, queryInput.getQueryJoins(), queryInput.getFieldsToTranslatePossibleValues());
|
||||
}
|
||||
|
@ -252,7 +252,7 @@ public class UpdateAction
|
||||
behaviorsToOmit = Set.of(DynamicDefaultValueBehavior.MODIFY_DATE);
|
||||
}
|
||||
|
||||
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.UPDATE, updateInput.getInstance(), table, updateInput.getRecords(), behaviorsToOmit);
|
||||
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.UPDATE, QContext.getQInstance(), table, updateInput.getRecords(), behaviorsToOmit);
|
||||
validatePrimaryKeysAreGiven(updateInput);
|
||||
|
||||
if(oldRecordList.isPresent())
|
||||
|
@ -68,7 +68,7 @@ public class QValueFormatter
|
||||
*******************************************************************************/
|
||||
public static String formatValue(QFieldMetaData field, Serializable value)
|
||||
{
|
||||
return (formatValue(field.getDisplayFormat(), field.getName(), value));
|
||||
return (formatValue(field.getDisplayFormat(), field.getType(), field.getName(), value));
|
||||
}
|
||||
|
||||
|
||||
@ -78,7 +78,7 @@ public class QValueFormatter
|
||||
*******************************************************************************/
|
||||
public static String formatValue(String displayFormat, Serializable value)
|
||||
{
|
||||
return (formatValue(displayFormat, "", value));
|
||||
return (formatValue(displayFormat, null, "", value));
|
||||
}
|
||||
|
||||
|
||||
@ -87,7 +87,7 @@ public class QValueFormatter
|
||||
** For a display format string, an optional fieldName (only used for logging),
|
||||
** and a value, apply the format.
|
||||
*******************************************************************************/
|
||||
private static String formatValue(String displayFormat, String fieldName, Serializable value)
|
||||
private static String formatValue(String displayFormat, QFieldType fieldType, String fieldName, Serializable value)
|
||||
{
|
||||
//////////////////////////////////
|
||||
// null values get null results //
|
||||
@ -107,6 +107,11 @@ public class QValueFormatter
|
||||
return formatBoolean(b);
|
||||
}
|
||||
|
||||
if(QFieldType.BOOLEAN.equals(fieldType))
|
||||
{
|
||||
return formatBoolean(ValueUtils.getValueAsBoolean(value));
|
||||
}
|
||||
|
||||
if(value instanceof LocalTime lt)
|
||||
{
|
||||
return formatLocalTime(lt);
|
||||
@ -404,6 +409,7 @@ public class QValueFormatter
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** For a single record, set its display values - where caller (meant to stay private)
|
||||
** can specify if they've already done fieldBehaviors (to avoid re-doing).
|
||||
@ -563,6 +569,7 @@ public class QValueFormatter
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// heavy fields that weren't fetched - they should have a backend-detail specifying their length (or null if null) //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Serializable> heavyFieldLengths = (Map<String, Serializable>) record.getBackendDetail(QRecord.BACKEND_DETAILS_TYPE_HEAVY_FIELD_LENGTHS);
|
||||
if(heavyFieldLengths != null)
|
||||
{
|
||||
|
@ -30,6 +30,7 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
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.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
@ -69,14 +70,14 @@ public class SearchPossibleValueSourceAction
|
||||
*******************************************************************************/
|
||||
public SearchPossibleValueSourceOutput execute(SearchPossibleValueSourceInput input) throws QException
|
||||
{
|
||||
QInstance qInstance = input.getInstance();
|
||||
QInstance qInstance = QContext.getQInstance();
|
||||
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(input.getPossibleValueSourceName());
|
||||
if(possibleValueSource == null)
|
||||
{
|
||||
throw new QException("Missing possible value source named [" + input.getPossibleValueSourceName() + "]");
|
||||
}
|
||||
|
||||
possibleValueTranslator = new QPossibleValueTranslator(input.getInstance(), input.getSession());
|
||||
possibleValueTranslator = new QPossibleValueTranslator(QContext.getQInstance(), QContext.getQSession());
|
||||
SearchPossibleValueSourceOutput output = null;
|
||||
if(possibleValueSource.getType().equals(QPossibleValueSourceType.ENUM))
|
||||
{
|
||||
@ -199,7 +200,7 @@ public class SearchPossibleValueSourceAction
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(possibleValueSource.getTableName());
|
||||
|
||||
QTableMetaData table = input.getInstance().getTable(possibleValueSource.getTableName());
|
||||
QTableMetaData table = QContext.getQInstance().getTable(possibleValueSource.getTableName());
|
||||
|
||||
QQueryFilter queryFilter = new QQueryFilter();
|
||||
queryFilter.setBooleanOperator(QQueryFilter.BooleanOperator.OR);
|
||||
@ -299,6 +300,7 @@ public class SearchPossibleValueSourceAction
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private SearchPossibleValueSourceOutput searchPossibleValueCustom(SearchPossibleValueSourceInput input, QPossibleValueSource possibleValueSource)
|
||||
{
|
||||
try
|
||||
|
@ -22,8 +22,8 @@
|
||||
package com.kingsrook.qqq.backend.core.exceptions;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
@ -55,12 +55,11 @@ public class QInstanceValidationException extends QException
|
||||
*******************************************************************************/
|
||||
public QInstanceValidationException(List<String> reasons)
|
||||
{
|
||||
super(
|
||||
(reasons != null && reasons.size() > 0)
|
||||
? "Instance validation failed for the following reasons:\n - " + StringUtils.join("\n - ", reasons)
|
||||
: "Validation failed, but no reasons were provided");
|
||||
super((CollectionUtils.nullSafeHasContents(reasons))
|
||||
? "Instance validation failed for the following reasons:\n - " + StringUtils.join("\n - ", reasons) + "\n(" + reasons.size() + " Total reason" + StringUtils.plural(reasons) + ")"
|
||||
: "Validation failed, but no reasons were provided");
|
||||
|
||||
if(reasons != null && reasons.size() > 0)
|
||||
if(CollectionUtils.nullSafeHasContents(reasons))
|
||||
{
|
||||
this.reasons = reasons;
|
||||
}
|
||||
@ -68,25 +67,6 @@ public class QInstanceValidationException extends QException
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor of an array/varargs of reasons. They feed into the core exception message.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QInstanceValidationException(String... reasons)
|
||||
{
|
||||
super(
|
||||
(reasons != null && reasons.length > 0)
|
||||
? "Instance validation failed for the following reasons: " + StringUtils.joinWithCommasAndAnd(Arrays.stream(reasons).toList())
|
||||
: "Validation failed, but no reasons were provided");
|
||||
|
||||
if(reasons != null && reasons.length > 0)
|
||||
{
|
||||
this.reasons = Arrays.stream(reasons).toList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor of message & cause - does not populate reasons!
|
||||
**
|
||||
|
@ -922,7 +922,7 @@ public class QInstanceValidator
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private <T extends FieldBehavior<T>> void validateTableField(QInstance qInstance, String tableName, String fieldName, QTableMetaData table, QFieldMetaData field)
|
||||
private void validateTableField(QInstance qInstance, String tableName, String fieldName, QTableMetaData table, QFieldMetaData field)
|
||||
{
|
||||
assertCondition(Objects.equals(fieldName, field.getName()),
|
||||
"Inconsistent naming in table " + tableName + " for field " + fieldName + "/" + field.getName() + ".");
|
||||
@ -944,12 +944,13 @@ public class QInstanceValidator
|
||||
assertCondition(field.getMaxLength() != null, prefix + "specifies a ValueTooLongBehavior, but not a maxLength.");
|
||||
}
|
||||
|
||||
Set<Class<FieldBehavior<T>>> usedFieldBehaviorTypes = new HashSet<>();
|
||||
Set<Class<FieldBehavior<?>>> usedFieldBehaviorTypes = new HashSet<>();
|
||||
if(field.getBehaviors() != null)
|
||||
{
|
||||
for(FieldBehavior<?> fieldBehavior : field.getBehaviors())
|
||||
{
|
||||
Class<FieldBehavior<T>> behaviorClass = (Class<FieldBehavior<T>>) fieldBehavior.getClass();
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<FieldBehavior<?>> behaviorClass = (Class<FieldBehavior<?>>) fieldBehavior.getClass();
|
||||
|
||||
errors.addAll(fieldBehavior.validateBehaviorConfiguration(table, field));
|
||||
|
||||
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.instances;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import com.amazonaws.auth.AWSStaticCredentialsProvider;
|
||||
@ -113,7 +114,7 @@ public class SecretsManagerUtils
|
||||
dotEnv.renameTo(new File(".env.backup-" + System.currentTimeMillis()));
|
||||
}
|
||||
|
||||
FileUtils.writeStringToFile(dotEnv, fullEnv.toString());
|
||||
FileUtils.writeStringToFile(dotEnv, fullEnv.toString(), StandardCharsets.UTF_8);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -280,6 +280,16 @@ public class QLogger
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void debug(LogPair... logPairs)
|
||||
{
|
||||
logger.warn(() -> makeJsonString(null, null, logPairs));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -420,6 +430,16 @@ public class QLogger
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void warn(LogPair... logPairs)
|
||||
{
|
||||
logger.warn(() -> makeJsonString(null, null, logPairs));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -480,6 +500,16 @@ public class QLogger
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void error(LogPair... logPairs)
|
||||
{
|
||||
logger.warn(() -> makeJsonString(null, null, logPairs));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -32,7 +32,6 @@ import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
||||
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.authentication.QAuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
|
||||
|
||||
@ -93,17 +92,6 @@ public class AbstractActionInput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@JsonIgnore
|
||||
public QAuthenticationMetaData getAuthenticationMetaData()
|
||||
{
|
||||
return (getInstance().getAuthentication());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for instance
|
||||
**
|
||||
|
@ -152,5 +152,8 @@ public class AuditDetailAccumulator implements Serializable
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private record TableNameAndPrimaryKey(String tableName, Serializable primaryKey) {}
|
||||
}
|
||||
|
@ -59,6 +59,30 @@ public class AggregateInput extends AbstractTableActionInput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AggregateInput(String tableName)
|
||||
{
|
||||
this();
|
||||
setTableName(tableName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public AggregateInput withTableName(String tableName)
|
||||
{
|
||||
super.withTableName(tableName);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for filter
|
||||
**
|
||||
|
@ -378,7 +378,7 @@ public class JoinsContext
|
||||
{
|
||||
securityFieldTableAlias = matchedQueryJoin.getJoinTableOrItsAlias();
|
||||
}
|
||||
tmpTable = instance.getTable(securityFieldTableAlias);
|
||||
tmpTable = instance.getTable(aliasToTableNameMap.getOrDefault(securityFieldTableAlias, securityFieldTableAlias));
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
// set the baseTableOrAlias for the next iteration to be this join's joinTableOrAlias //
|
||||
@ -466,8 +466,8 @@ public class JoinsContext
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// check if the key type has an all-access key, and if so, if it's set to true for the current user/session //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QSecurityKeyType securityKeyType = instance.getSecurityKeyType(recordSecurityLock.getSecurityKeyType());
|
||||
boolean haveAllAccessKey = false;
|
||||
QSecurityKeyType securityKeyType = instance.getSecurityKeyType(recordSecurityLock.getSecurityKeyType());
|
||||
boolean haveAllAccessKey = false;
|
||||
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()))
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -1118,7 +1118,7 @@ public class JoinsContext
|
||||
if(useExposedJoins)
|
||||
{
|
||||
QTableMetaData mainTable = QContext.getQInstance().getTable(mainTableName);
|
||||
for(ExposedJoin exposedJoin : mainTable.getExposedJoins())
|
||||
for(ExposedJoin exposedJoin : CollectionUtils.nonNullList(mainTable.getExposedJoins()))
|
||||
{
|
||||
if(exposedJoin.getJoinTable().equals(joinTableName))
|
||||
{
|
||||
@ -1159,6 +1159,7 @@ public class JoinsContext
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -25,6 +25,7 @@ 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.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -237,6 +238,28 @@ public class QQueryFilter implements Serializable, Cloneable
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** fluent method to add a new criteria
|
||||
*******************************************************************************/
|
||||
public QQueryFilter withCriteria(String fieldName, QCriteriaOperator operator, Collection<? extends Serializable> values)
|
||||
{
|
||||
addCriteria(new QFilterCriteria(fieldName, operator, values));
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** fluent method to add a new criteria
|
||||
*******************************************************************************/
|
||||
public QQueryFilter withCriteria(String fieldName, QCriteriaOperator operator, Serializable... values)
|
||||
{
|
||||
addCriteria(new QFilterCriteria(fieldName, operator, values));
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -56,7 +56,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
** JoinsContext is constructed before executing a query, and not meant to be set
|
||||
** by users.
|
||||
*******************************************************************************/
|
||||
public class QueryJoin
|
||||
public class QueryJoin implements Cloneable
|
||||
{
|
||||
private String baseTableOrAlias;
|
||||
private String joinTable;
|
||||
@ -69,6 +69,40 @@ public class QueryJoin
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QueryJoin clone()
|
||||
{
|
||||
try
|
||||
{
|
||||
QueryJoin clone = (QueryJoin) super.clone();
|
||||
|
||||
if(joinMetaData != null)
|
||||
{
|
||||
clone.joinMetaData = joinMetaData.clone();
|
||||
}
|
||||
|
||||
if(securityCriteria != null)
|
||||
{
|
||||
clone.securityCriteria = new ArrayList<>();
|
||||
for(QFilterCriteria securityCriterion : securityCriteria)
|
||||
{
|
||||
clone.securityCriteria.add(securityCriterion.clone());
|
||||
}
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
catch(CloneNotSupportedException e)
|
||||
{
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** define the types of joins - INNER, LEFT, RIGHT, or FULL.
|
||||
*******************************************************************************/
|
||||
|
@ -40,11 +40,14 @@ public abstract class AbstractFilterExpression<T extends Serializable> implement
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Evaluate the expression, given a map of input values.
|
||||
**
|
||||
** By default, this will defer to the evaluate(void) method - but, a subclass
|
||||
** (e.g., FilterVariableExpression) may react differently.
|
||||
*******************************************************************************/
|
||||
public T evaluateInputValues(Map<String, Serializable> inputValues) throws QException
|
||||
{
|
||||
return (T) this;
|
||||
return evaluate();
|
||||
}
|
||||
|
||||
|
||||
|
@ -42,6 +42,9 @@ public class NowWithOffset extends AbstractFilterExpression<Instant>
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public enum Operator
|
||||
{PLUS, MINUS}
|
||||
|
||||
|
@ -43,6 +43,9 @@ public class ThisOrLastPeriod extends AbstractFilterExpression<Instant>
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public enum Operator
|
||||
{THIS, LAST}
|
||||
|
||||
|
@ -79,10 +79,11 @@ public class QFilterCriteriaDeserializer extends StdDeserializer<QFilterCriteria
|
||||
/////////////////////////////////
|
||||
// get values out of json node //
|
||||
/////////////////////////////////
|
||||
List<Serializable> values = objectMapper.treeToValue(node.get("values"), List.class);
|
||||
String fieldName = objectMapper.treeToValue(node.get("fieldName"), String.class);
|
||||
QCriteriaOperator operator = objectMapper.treeToValue(node.get("operator"), QCriteriaOperator.class);
|
||||
String otherFieldName = objectMapper.treeToValue(node.get("otherFieldName"), String.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Serializable> values = objectMapper.treeToValue(node.get("values"), List.class);
|
||||
String fieldName = objectMapper.treeToValue(node.get("fieldName"), String.class);
|
||||
QCriteriaOperator operator = objectMapper.treeToValue(node.get("operator"), QCriteriaOperator.class);
|
||||
String otherFieldName = objectMapper.treeToValue(node.get("otherFieldName"), String.class);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// look at all the values - if any of them are actually meant to be an Expression (instance of subclass of AbstractFilterExpression) //
|
||||
|
@ -28,6 +28,9 @@ package com.kingsrook.qqq.backend.core.model.dashboard.widgets;
|
||||
*******************************************************************************/
|
||||
public class AlertData extends QWidgetData
|
||||
{
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public enum AlertType
|
||||
{
|
||||
ERROR,
|
||||
|
@ -74,6 +74,7 @@ public abstract class AbstractBlockWidgetData<
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public T withTooltip(S key, String value)
|
||||
{
|
||||
addTooltip(key, value);
|
||||
@ -99,6 +100,7 @@ public abstract class AbstractBlockWidgetData<
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public T withTooltip(S key, BlockTooltip value)
|
||||
{
|
||||
addTooltip(key, value);
|
||||
@ -144,6 +146,7 @@ public abstract class AbstractBlockWidgetData<
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tooltipMap
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public T withTooltipMap(Map<S, BlockTooltip> tooltipMap)
|
||||
{
|
||||
this.tooltipMap = tooltipMap;
|
||||
@ -178,6 +181,7 @@ public abstract class AbstractBlockWidgetData<
|
||||
** Fluent setter for tooltip
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public T withTooltip(String tooltip)
|
||||
{
|
||||
this.tooltip = new BlockTooltip(tooltip);
|
||||
@ -190,6 +194,7 @@ public abstract class AbstractBlockWidgetData<
|
||||
** Fluent setter for tooltip
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public T withTooltip(BlockTooltip tooltip)
|
||||
{
|
||||
this.tooltip = tooltip;
|
||||
@ -201,6 +206,7 @@ public abstract class AbstractBlockWidgetData<
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public T withLink(S key, String value)
|
||||
{
|
||||
addLink(key, value);
|
||||
@ -226,6 +232,7 @@ public abstract class AbstractBlockWidgetData<
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public T withLink(S key, BlockLink value)
|
||||
{
|
||||
addLink(key, value);
|
||||
@ -271,6 +278,7 @@ public abstract class AbstractBlockWidgetData<
|
||||
/*******************************************************************************
|
||||
** Fluent setter for linkMap
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public T withLinkMap(Map<S, BlockLink> linkMap)
|
||||
{
|
||||
this.linkMap = linkMap;
|
||||
@ -305,6 +313,7 @@ public abstract class AbstractBlockWidgetData<
|
||||
** Fluent setter for link
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public T withLink(String link)
|
||||
{
|
||||
this.link = new BlockLink(link);
|
||||
@ -317,6 +326,7 @@ public abstract class AbstractBlockWidgetData<
|
||||
** Fluent setter for link
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public T withLink(BlockLink link)
|
||||
{
|
||||
this.link = link;
|
||||
@ -348,6 +358,7 @@ public abstract class AbstractBlockWidgetData<
|
||||
/*******************************************************************************
|
||||
** Fluent setter for values
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public T withValues(V values)
|
||||
{
|
||||
this.values = values;
|
||||
@ -379,6 +390,7 @@ public abstract class AbstractBlockWidgetData<
|
||||
/*******************************************************************************
|
||||
** Fluent setter for styles
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public T withStyles(SX styles)
|
||||
{
|
||||
this.styles = styles;
|
||||
@ -409,6 +421,7 @@ public abstract class AbstractBlockWidgetData<
|
||||
/*******************************************************************************
|
||||
** Fluent setter for blockId
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public T withBlockId(String blockId)
|
||||
{
|
||||
this.blockId = blockId;
|
||||
|
@ -33,6 +33,9 @@ public class BlockTooltip
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public enum Placement
|
||||
{BOTTOM, LEFT, RIGHT, TOP}
|
||||
|
||||
|
@ -192,6 +192,7 @@ public abstract class QRecordEntity
|
||||
|
||||
for(QRecordEntityAssociation qRecordEntityAssociation : getAssociationList(this.getClass()))
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
List<? extends QRecordEntity> associatedEntities = (List<? extends QRecordEntity>) qRecordEntityAssociation.getGetter().invoke(this);
|
||||
String associationName = qRecordEntityAssociation.getAssociationAnnotation().name();
|
||||
|
||||
@ -245,6 +246,7 @@ public abstract class QRecordEntity
|
||||
|
||||
for(QRecordEntityAssociation qRecordEntityAssociation : getAssociationList(this.getClass()))
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
List<? extends QRecordEntity> associatedEntities = (List<? extends QRecordEntity>) qRecordEntityAssociation.getGetter().invoke(this);
|
||||
String associationName = qRecordEntityAssociation.getAssociationAnnotation().name();
|
||||
|
||||
@ -346,6 +348,7 @@ public abstract class QRecordEntity
|
||||
|
||||
if(associationAnnotation.isPresent())
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends QRecordEntity> listTypeParam = (Class<? extends QRecordEntity>) getListTypeParam(possibleGetter.getReturnType(), possibleGetter.getAnnotatedReturnType());
|
||||
associationList.add(new QRecordEntityAssociation(fieldName, possibleGetter, setter.get(), listTypeParam, associationAnnotation.orElse(null)));
|
||||
}
|
||||
|
@ -53,6 +53,8 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
|
||||
private String variantOptionsTableUsernameField;
|
||||
private String variantOptionsTablePasswordField;
|
||||
private String variantOptionsTableApiKeyField;
|
||||
private String variantOptionsTableClientIdField;
|
||||
private String variantOptionsTableClientSecretField;
|
||||
private String variantOptionsTableName;
|
||||
|
||||
// todo - at some point, we may want to apply this to secret properties on subclasses?
|
||||
@ -648,4 +650,66 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
|
||||
{
|
||||
qInstance.addBackend(this);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantOptionsTableClientIdField
|
||||
*******************************************************************************/
|
||||
public String getVariantOptionsTableClientIdField()
|
||||
{
|
||||
return (this.variantOptionsTableClientIdField);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantOptionsTableClientIdField
|
||||
*******************************************************************************/
|
||||
public void setVariantOptionsTableClientIdField(String variantOptionsTableClientIdField)
|
||||
{
|
||||
this.variantOptionsTableClientIdField = variantOptionsTableClientIdField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantOptionsTableClientIdField
|
||||
*******************************************************************************/
|
||||
public QBackendMetaData withVariantOptionsTableClientIdField(String variantOptionsTableClientIdField)
|
||||
{
|
||||
this.variantOptionsTableClientIdField = variantOptionsTableClientIdField;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantOptionsTableClientSecretField
|
||||
*******************************************************************************/
|
||||
public String getVariantOptionsTableClientSecretField()
|
||||
{
|
||||
return (this.variantOptionsTableClientSecretField);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for variantOptionsTableClientSecretField
|
||||
*******************************************************************************/
|
||||
public void setVariantOptionsTableClientSecretField(String variantOptionsTableClientSecretField)
|
||||
{
|
||||
this.variantOptionsTableClientSecretField = variantOptionsTableClientSecretField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for variantOptionsTableClientSecretField
|
||||
*******************************************************************************/
|
||||
public QBackendMetaData withVariantOptionsTableClientSecretField(String variantOptionsTableClientSecretField)
|
||||
{
|
||||
this.variantOptionsTableClientSecretField = variantOptionsTableClientSecretField;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -39,6 +39,9 @@ public class ParentWidgetMetaData extends QWidgetMetaData
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public enum LayoutType
|
||||
{
|
||||
GRID,
|
||||
|
@ -188,7 +188,8 @@ public class FieldAdornment
|
||||
** Fluent setter for values
|
||||
**
|
||||
*******************************************************************************/
|
||||
public FieldAdornment withValues(Pair<String, Serializable>... values)
|
||||
@SafeVarargs
|
||||
public final FieldAdornment withValues(Pair<String, Serializable>... values)
|
||||
{
|
||||
for(Pair<String, Serializable> value : values)
|
||||
{
|
||||
|
@ -168,11 +168,11 @@ public class QFrontendTableMetaData
|
||||
editPermission = PermissionsHelper.hasTablePermission(actionInput, tableMetaData.getName(), TablePermissionSubType.EDIT);
|
||||
deletePermission = PermissionsHelper.hasTablePermission(actionInput, tableMetaData.getName(), TablePermissionSubType.DELETE);
|
||||
|
||||
QBackendMetaData backend = actionInput.getInstance().getBackend(tableMetaData.getBackendName());
|
||||
QBackendMetaData backend = QContext.getQInstance().getBackend(tableMetaData.getBackendName());
|
||||
if(backend != null && backend.getUsesVariants())
|
||||
{
|
||||
usesVariants = true;
|
||||
variantTableLabel = actionInput.getInstance().getTable(backend.getVariantOptionsTableName()).getLabel();
|
||||
variantTableLabel = QContext.getQInstance().getTable(backend.getVariantOptionsTableName()).getLabel();
|
||||
}
|
||||
|
||||
this.helpContents = tableMetaData.getHelpContent();
|
||||
|
@ -26,7 +26,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.joins;
|
||||
** Specification for (at least part of) how two tables join together - e.g.,
|
||||
** leftField = rightField. Used as part of a list in a QJoinMetaData.
|
||||
*******************************************************************************/
|
||||
public class JoinOn
|
||||
public class JoinOn implements Cloneable
|
||||
{
|
||||
private String leftField;
|
||||
private String rightField;
|
||||
@ -131,4 +131,22 @@ public class JoinOn
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public JoinOn clone()
|
||||
{
|
||||
try
|
||||
{
|
||||
JoinOn clone = (JoinOn) super.clone();
|
||||
return clone;
|
||||
}
|
||||
catch(CloneNotSupportedException e)
|
||||
{
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
/*******************************************************************************
|
||||
** Definition of how 2 tables join together within a QQQ Instance.
|
||||
*******************************************************************************/
|
||||
public class QJoinMetaData implements TopLevelMetaDataInterface
|
||||
public class QJoinMetaData implements TopLevelMetaDataInterface, Cloneable
|
||||
{
|
||||
private String name;
|
||||
private JoinType type;
|
||||
@ -62,6 +62,44 @@ public class QJoinMetaData implements TopLevelMetaDataInterface
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QJoinMetaData clone()
|
||||
{
|
||||
try
|
||||
{
|
||||
QJoinMetaData clone = (QJoinMetaData) super.clone();
|
||||
|
||||
if(joinOns != null)
|
||||
{
|
||||
clone.joinOns = new ArrayList<>();
|
||||
for(JoinOn joinOn : joinOns)
|
||||
{
|
||||
clone.joinOns.add(joinOn.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if(orderBys != null)
|
||||
{
|
||||
clone.orderBys = new ArrayList<>();
|
||||
for(QFilterOrderBy orderBy : orderBys)
|
||||
{
|
||||
clone.orderBys.add(orderBy.clone());
|
||||
}
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
catch(CloneNotSupportedException e)
|
||||
{
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for name
|
||||
**
|
||||
|
@ -804,7 +804,7 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
{
|
||||
if(this.associatedScripts == null)
|
||||
{
|
||||
this.associatedScripts = new ArrayList();
|
||||
this.associatedScripts = new ArrayList<>();
|
||||
}
|
||||
this.associatedScripts.add(associatedScript);
|
||||
return (this);
|
||||
|
@ -32,6 +32,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
*******************************************************************************/
|
||||
public class CacheUseCase
|
||||
{
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public enum Type
|
||||
{
|
||||
PRIMARY_KEY_TO_PRIMARY_KEY, // e.g., the primary key in the cache table equals the primary key in the source table.
|
||||
|
@ -662,7 +662,11 @@ public class MemoryRecordStore
|
||||
{
|
||||
return (-1);
|
||||
}
|
||||
return ((Comparable) a).compareTo(b);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Comparable<Serializable> comparableSerializableA = (Comparable<Serializable>) a;
|
||||
|
||||
return comparableSerializableA.compareTo(b);
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -769,6 +773,7 @@ public class MemoryRecordStore
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private static Serializable computeAggregate(List<QRecord> records, Aggregate aggregate, QTableMetaData table)
|
||||
{
|
||||
String fieldName = aggregate.getFieldName();
|
||||
|
@ -27,6 +27,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLine;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLineInterface;
|
||||
@ -76,7 +77,7 @@ public class BulkDeleteLoadStep extends LoadViaDeleteStep implements ProcessSumm
|
||||
{
|
||||
super.preRun(runBackendStepInput, runBackendStepOutput);
|
||||
|
||||
QTableMetaData table = runBackendStepInput.getInstance().getTable(runBackendStepInput.getTableName());
|
||||
QTableMetaData table = QContext.getQInstance().getTable(runBackendStepInput.getTableName());
|
||||
if(table != null)
|
||||
{
|
||||
tableLabel = table.getLabel();
|
||||
@ -119,7 +120,7 @@ public class BulkDeleteLoadStep extends LoadViaDeleteStep implements ProcessSumm
|
||||
////////////////////////////
|
||||
super.runOnePage(runBackendStepInput, runBackendStepOutput);
|
||||
|
||||
QTableMetaData table = runBackendStepInput.getInstance().getTable(runBackendStepInput.getTableName());
|
||||
QTableMetaData table = QContext.getQInstance().getTable(runBackendStepInput.getTableName());
|
||||
String primaryKeyFieldName = table.getPrimaryKeyField();
|
||||
Map<Serializable, QRecord> outputRecordMap = runBackendStepOutput.getRecords().stream().collect(Collectors.toMap(r -> r.getValue(primaryKeyFieldName), r -> r, (a, b) -> a));
|
||||
|
||||
|
@ -27,6 +27,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLine;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLineInterface;
|
||||
@ -65,7 +66,7 @@ public class BulkDeleteTransformStep extends AbstractTransformStep
|
||||
///////////////////////////////////////////////////////
|
||||
// capture the table label - for the process summary //
|
||||
///////////////////////////////////////////////////////
|
||||
QTableMetaData table = runBackendStepInput.getInstance().getTable(runBackendStepInput.getTableName());
|
||||
QTableMetaData table = QContext.getQInstance().getTable(runBackendStepInput.getTableName());
|
||||
if(table != null)
|
||||
{
|
||||
tableLabel = table.getLabel();
|
||||
|
@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLine;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLineInterface;
|
||||
@ -99,7 +100,7 @@ public class BulkEditLoadStep extends LoadViaUpdateStep implements ProcessSummar
|
||||
{
|
||||
super.preRun(runBackendStepInput, runBackendStepOutput);
|
||||
|
||||
QTableMetaData table = runBackendStepInput.getInstance().getTable(runBackendStepInput.getTableName());
|
||||
QTableMetaData table = QContext.getQInstance().getTable(runBackendStepInput.getTableName());
|
||||
if(table != null)
|
||||
{
|
||||
tableLabel = table.getLabel();
|
||||
@ -124,7 +125,7 @@ public class BulkEditLoadStep extends LoadViaUpdateStep implements ProcessSummar
|
||||
////////////////////////////////////////////////////////
|
||||
// roll up results based on output from update action //
|
||||
////////////////////////////////////////////////////////
|
||||
QTableMetaData table = runBackendStepInput.getInstance().getTable(runBackendStepInput.getTableName());
|
||||
QTableMetaData table = QContext.getQInstance().getTable(runBackendStepInput.getTableName());
|
||||
for(QRecord record : runBackendStepOutput.getRecords())
|
||||
{
|
||||
Serializable recordPrimaryKey = record.getValue(table.getPrimaryKeyField());
|
||||
|
@ -31,6 +31,7 @@ import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLine;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLineInterface;
|
||||
@ -81,7 +82,7 @@ public class BulkEditTransformStep extends AbstractTransformStep
|
||||
///////////////////////////////////////////////////////
|
||||
// capture the table label - for the process summary //
|
||||
///////////////////////////////////////////////////////
|
||||
table = runBackendStepInput.getInstance().getTable(runBackendStepInput.getTableName());
|
||||
table = QContext.getQInstance().getTable(runBackendStepInput.getTableName());
|
||||
if(table != null)
|
||||
{
|
||||
tableLabel = table.getLabel();
|
||||
@ -230,7 +231,7 @@ public class BulkEditTransformStep extends AbstractTransformStep
|
||||
|
||||
if(field.getPossibleValueSourceName() != null)
|
||||
{
|
||||
QPossibleValueTranslator qPossibleValueTranslator = new QPossibleValueTranslator(runBackendStepInput.getInstance(), runBackendStepInput.getSession());
|
||||
QPossibleValueTranslator qPossibleValueTranslator = new QPossibleValueTranslator(QContext.getQInstance(), QContext.getQSession());
|
||||
String translatedValue = qPossibleValueTranslator.translatePossibleValue(field, value);
|
||||
if(StringUtils.hasContent(translatedValue))
|
||||
{
|
||||
|
@ -27,6 +27,7 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.adapters.CsvToQRecordAdapter;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.QUploadedFile;
|
||||
@ -83,7 +84,7 @@ public class BulkInsertExtractStep extends AbstractExtractStep
|
||||
.withLimit(getLimit())
|
||||
.withCsv(new String(bytes))
|
||||
.withDoCorrectValueTypes(true)
|
||||
.withTable(runBackendStepInput.getInstance().getTable(tableName))
|
||||
.withTable(QContext.getQInstance().getTable(tableName))
|
||||
.withMapping(mapping)
|
||||
.withRecordCustomizer((record) ->
|
||||
{
|
||||
|
@ -38,6 +38,7 @@ import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterfa
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.UniqueKeyHelper;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLine;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLineInterface;
|
||||
@ -104,7 +105,7 @@ public class BulkInsertTransformStep extends AbstractTransformStep
|
||||
@Override
|
||||
public void preRun(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
|
||||
{
|
||||
this.table = runBackendStepInput.getInstance().getTable(runBackendStepInput.getTableName());
|
||||
this.table = QContext.getQInstance().getTable(runBackendStepInput.getTableName());
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// since we're doing a unique key check in this class, we can tell the loadViaInsert step that it (rather, the InsertAction) doesn't need to re-do one. //
|
||||
@ -121,7 +122,7 @@ public class BulkInsertTransformStep extends AbstractTransformStep
|
||||
public void runOnePage(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
|
||||
{
|
||||
int rowsInThisPage = runBackendStepInput.getRecords().size();
|
||||
QTableMetaData table = runBackendStepInput.getInstance().getTable(runBackendStepInput.getTableName());
|
||||
QTableMetaData table = QContext.getQInstance().getTable(runBackendStepInput.getTableName());
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// set up an insert-input, which will be used as input to the pre-customizer as well as for additional validations //
|
||||
|
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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.processes.implementations.columnstats;
|
||||
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin;
|
||||
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.ExceptionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.Pair;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility for verifying that the ColumnStats process works for all fields,
|
||||
** on all tables, and all exposed joins.
|
||||
**
|
||||
** Meant for use within a unit test, or maybe as part of an instance's boot-up/
|
||||
** validation.
|
||||
*******************************************************************************/
|
||||
public class ColumnStatsFullInstanceVerifier
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(ColumnStatsFullInstanceVerifier.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void verify(Collection<QTableMetaData> tables) throws QException
|
||||
{
|
||||
Map<Pair<String, String>, Exception> caughtExceptions = new LinkedHashMap<>();
|
||||
for(QTableMetaData table : tables)
|
||||
{
|
||||
if(table.isCapabilityEnabled(QContext.getQInstance().getBackendForTable(table.getName()), Capability.QUERY_STATS))
|
||||
{
|
||||
LOG.info("Verifying ColumnStats on table", logPair("tableName", table.getName()));
|
||||
for(QFieldMetaData field : table.getFields().values())
|
||||
{
|
||||
runColumnStats(table.getName(), field.getName(), caughtExceptions);
|
||||
}
|
||||
|
||||
for(ExposedJoin exposedJoin : CollectionUtils.nonNullList(table.getExposedJoins()))
|
||||
{
|
||||
QTableMetaData joinTable = QContext.getQInstance().getTable(exposedJoin.getJoinTable());
|
||||
for(QFieldMetaData field : joinTable.getFields().values())
|
||||
{
|
||||
runColumnStats(table.getName(), joinTable.getName() + "." + field.getName(), caughtExceptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// log out an exceptions caught
|
||||
if(!caughtExceptions.isEmpty())
|
||||
{
|
||||
for(Map.Entry<Pair<String, String>, Exception> entry : caughtExceptions.entrySet())
|
||||
{
|
||||
LOG.info("Caught an exception verifying column stats", entry.getValue(), logPair("tableName", entry.getKey().getA()), logPair("fieldName", entry.getKey().getB()));
|
||||
}
|
||||
throw (new QException("Column Status Verification failed with " + caughtExceptions.size() + " exception" + StringUtils.plural(caughtExceptions.size())));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void runColumnStats(String tableName, String fieldName, Map<Pair<String, String>, Exception> caughtExceptions) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
RunBackendStepInput input = new RunBackendStepInput();
|
||||
input.addValue("tableName", tableName);
|
||||
input.addValue("fieldName", fieldName);
|
||||
RunBackendStepOutput output = new RunBackendStepOutput();
|
||||
new ColumnStatsStep().run(input, output);
|
||||
}
|
||||
catch(QException e)
|
||||
{
|
||||
Throwable rootException = ExceptionUtils.getRootException(e);
|
||||
if(rootException instanceof QException && rootException.getMessage().contains("not supported for this field's data type"))
|
||||
{
|
||||
////////////////////////////////////////////////
|
||||
// ignore this exception, it's kinda expected //
|
||||
////////////////////////////////////////////////
|
||||
LOG.debug("Caught an expected-exception in column stats", e, logPair("tableName", tableName), logPair("fieldName", fieldName));
|
||||
}
|
||||
else
|
||||
{
|
||||
caughtExceptions.put(Pair.of(tableName, fieldName), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -136,29 +136,11 @@ public class ColumnStatsStep implements BackendStep
|
||||
filter = new QQueryFilter();
|
||||
}
|
||||
|
||||
QueryJoin queryJoin = null;
|
||||
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||
QFieldMetaData field = null;
|
||||
if(fieldName.contains("."))
|
||||
{
|
||||
String[] parts = fieldName.split("\\.", 2);
|
||||
for(ExposedJoin exposedJoin : CollectionUtils.nonNullList(table.getExposedJoins()))
|
||||
{
|
||||
if(exposedJoin.getJoinTable().equals(parts[0]))
|
||||
{
|
||||
field = QContext.getQInstance().getTable(exposedJoin.getJoinTable()).getField(parts[1]);
|
||||
queryJoin = new QueryJoin()
|
||||
.withJoinTable(exposedJoin.getJoinTable())
|
||||
.withSelect(true)
|
||||
.withType(QueryJoin.Type.INNER);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
field = table.getField(fieldName);
|
||||
}
|
||||
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||
|
||||
FieldAndQueryJoin fieldAndQueryJoin = getFieldAndQueryJoin(table, fieldName);
|
||||
QFieldMetaData field = fieldAndQueryJoin.field();
|
||||
QueryJoin queryJoin = fieldAndQueryJoin.queryJoin();
|
||||
|
||||
if(field == null)
|
||||
{
|
||||
@ -213,7 +195,7 @@ public class ColumnStatsStep implements BackendStep
|
||||
filter.withOrderBy(new QFilterOrderByAggregate(aggregate, false));
|
||||
filter.withOrderBy(new QFilterOrderByGroupBy(groupBy));
|
||||
|
||||
Integer limit = 1000; // too big?
|
||||
Integer limit = 1000;
|
||||
AggregateInput aggregateInput = new AggregateInput();
|
||||
aggregateInput.withAggregate(aggregate);
|
||||
aggregateInput.withGroupBy(groupBy);
|
||||
@ -223,7 +205,11 @@ public class ColumnStatsStep implements BackendStep
|
||||
|
||||
if(queryJoin != null)
|
||||
{
|
||||
aggregateInput.withQueryJoin(queryJoin);
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// re-construct this queryJoin object - just because, the JoinsContext edits the previous one, so we can make some failing-joins otherwise... //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
fieldAndQueryJoin = getFieldAndQueryJoin(table, fieldName);
|
||||
aggregateInput.withQueryJoin(fieldAndQueryJoin.queryJoin());
|
||||
}
|
||||
|
||||
AggregateOutput aggregateOutput = new AggregateAction().execute(aggregateInput);
|
||||
@ -238,7 +224,7 @@ public class ColumnStatsStep implements BackendStep
|
||||
value = Instant.parse(value + ":00:00Z");
|
||||
}
|
||||
|
||||
Integer count = ValueUtils.getValueAsInteger(result.getAggregateValue(aggregate));
|
||||
Integer count = ValueUtils.getValueAsInteger(result.getAggregateValue(aggregate));
|
||||
valueCounts.add(new QRecord().withValue(fieldName, value).withValue("count", count));
|
||||
}
|
||||
|
||||
@ -526,4 +512,43 @@ public class ColumnStatsStep implements BackendStep
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private FieldAndQueryJoin getFieldAndQueryJoin(QTableMetaData table, String fieldName)
|
||||
{
|
||||
QFieldMetaData field = null;
|
||||
QueryJoin queryJoin = null;
|
||||
if(fieldName.contains("."))
|
||||
{
|
||||
String[] parts = fieldName.split("\\.", 2);
|
||||
for(ExposedJoin exposedJoin : CollectionUtils.nonNullList(table.getExposedJoins()))
|
||||
{
|
||||
if(exposedJoin.getJoinTable().equals(parts[0]))
|
||||
{
|
||||
field = QContext.getQInstance().getTable(exposedJoin.getJoinTable()).getField(parts[1]);
|
||||
queryJoin = new QueryJoin()
|
||||
.withJoinTable(exposedJoin.getJoinTable())
|
||||
.withSelect(true)
|
||||
.withType(QueryJoin.Type.INNER);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
field = table.getField(fieldName);
|
||||
}
|
||||
|
||||
return (new FieldAndQueryJoin(field, queryJoin));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private record FieldAndQueryJoin(QFieldMetaData field, QueryJoin queryJoin) {}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.adapters.JsonToQFieldMappingAdapter;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
@ -85,7 +86,7 @@ public class BasicETLTransformFunction implements BackendStep
|
||||
throw (new QException("Mapping was not a Key-based mapping type. Was a : " + mapping.getClass().getName()));
|
||||
}
|
||||
|
||||
QTableMetaData table = runBackendStepInput.getInstance().getTable(tableName);
|
||||
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||
List<QRecord> mappedRecords = applyMapping(runBackendStepInput.getRecords(), table, keyBasedFieldMapping);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -27,6 +27,7 @@ import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
@ -85,14 +86,14 @@ public class BaseStreamedETLStep
|
||||
protected void updateRecordsWithDisplayValuesAndPossibleValues(RunBackendStepInput input, List<QRecord> list)
|
||||
{
|
||||
String destinationTable = input.getValueString(StreamedETLWithFrontendProcess.FIELD_DESTINATION_TABLE);
|
||||
QTableMetaData table = input.getInstance().getTable(destinationTable);
|
||||
QTableMetaData table = QContext.getQInstance().getTable(destinationTable);
|
||||
|
||||
if(table != null && list != null)
|
||||
{
|
||||
QValueFormatter qValueFormatter = new QValueFormatter();
|
||||
qValueFormatter.setDisplayValuesInRecords(table, list);
|
||||
|
||||
QPossibleValueTranslator qPossibleValueTranslator = new QPossibleValueTranslator(input.getInstance(), input.getSession());
|
||||
QPossibleValueTranslator qPossibleValueTranslator = new QPossibleValueTranslator(QContext.getQInstance(), QContext.getQSession());
|
||||
qPossibleValueTranslator.translatePossibleValuesInRecords(table, list);
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ import com.kingsrook.qqq.backend.core.actions.reporting.DistinctFilteringRecordP
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
||||
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.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
@ -267,7 +268,7 @@ public class ExtractViaQueryStep extends AbstractExtractStep
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// else, check for recordIds from a frontend launching of a process //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
QTableMetaData table = runBackendStepInput.getInstance().getTable(runBackendStepInput.getValueString(FIELD_SOURCE_TABLE));
|
||||
QTableMetaData table = QContext.getQInstance().getTable(runBackendStepInput.getValueString(FIELD_SOURCE_TABLE));
|
||||
if(table == null)
|
||||
{
|
||||
throw (new QException("source table name was not set - could not load records by id"));
|
||||
@ -319,7 +320,7 @@ public class ExtractViaQueryStep extends AbstractExtractStep
|
||||
if(needDistinctPipe)
|
||||
{
|
||||
String sourceTableName = runBackendStepInput.getValueString(StreamedETLWithFrontendProcess.FIELD_SOURCE_TABLE);
|
||||
QTableMetaData sourceTable = runBackendStepInput.getInstance().getTable(sourceTableName);
|
||||
QTableMetaData sourceTable = QContext.getQInstance().getTable(sourceTableName);
|
||||
return (new DistinctFilteringRecordPipe(new UniqueKey(sourceTable.getPrimaryKeyField()), overrideCapacity));
|
||||
}
|
||||
else
|
||||
|
@ -28,6 +28,7 @@ import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
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.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
@ -86,7 +87,7 @@ public class LoadViaInsertOrUpdateStep extends AbstractLoadStep
|
||||
*******************************************************************************/
|
||||
public void insertAndUpdateRecords(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
|
||||
{
|
||||
QTableMetaData tableMetaData = runBackendStepInput.getInstance().getTable(runBackendStepInput.getValueString(FIELD_DESTINATION_TABLE));
|
||||
QTableMetaData tableMetaData = QContext.getQInstance().getTable(runBackendStepInput.getValueString(FIELD_DESTINATION_TABLE));
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(recordsToInsert))
|
||||
{
|
||||
@ -139,7 +140,7 @@ public class LoadViaInsertOrUpdateStep extends AbstractLoadStep
|
||||
*******************************************************************************/
|
||||
protected void evaluateRecords(RunBackendStepInput runBackendStepInput) throws QException
|
||||
{
|
||||
QTableMetaData tableMetaData = runBackendStepInput.getInstance().getTable(runBackendStepInput.getValueString(FIELD_DESTINATION_TABLE));
|
||||
QTableMetaData tableMetaData = QContext.getQInstance().getTable(runBackendStepInput.getValueString(FIELD_DESTINATION_TABLE));
|
||||
recordsToInsert = new ArrayList<>();
|
||||
recordsToUpdate = new ArrayList<>();
|
||||
|
||||
|
@ -162,10 +162,10 @@ public class StreamedETLPreviewStep extends BaseStreamedETLStep implements Backe
|
||||
private void countRecords(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput, AbstractExtractStep extractStep) throws QException
|
||||
{
|
||||
String sourceTableName = runBackendStepInput.getValueString(StreamedETLWithFrontendProcess.FIELD_SOURCE_TABLE);
|
||||
QTableMetaData sourceTable = runBackendStepInput.getInstance().getTable(sourceTableName);
|
||||
QTableMetaData sourceTable = QContext.getQInstance().getTable(sourceTableName);
|
||||
if(StringUtils.hasContent(sourceTableName))
|
||||
{
|
||||
QBackendMetaData sourceTableBackend = runBackendStepInput.getInstance().getBackendForTable(sourceTableName);
|
||||
QBackendMetaData sourceTableBackend = QContext.getQInstance().getBackendForTable(sourceTableName);
|
||||
if(sourceTable.isCapabilityEnabled(sourceTableBackend, Capability.TABLE_COUNT))
|
||||
{
|
||||
Integer recordCount = extractStep.doCount(runBackendStepInput);
|
||||
|
@ -65,9 +65,14 @@ public class MergeDuplicatesLoadStep extends LoadViaInsertOrUpdateStep
|
||||
{
|
||||
super.runOnePage(runBackendStepInput, runBackendStepOutput);
|
||||
|
||||
ListingHash<String, Serializable> otherTableIdsToDelete = (ListingHash<String, Serializable>) runBackendStepInput.getValue("otherTableIdsToDelete");
|
||||
@SuppressWarnings("unchecked")
|
||||
ListingHash<String, Serializable> otherTableIdsToDelete = (ListingHash<String, Serializable>) runBackendStepInput.getValue("otherTableIdsToDelete");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ListingHash<String, QQueryFilter> otherTableFiltersToDelete = (ListingHash<String, QQueryFilter>) runBackendStepInput.getValue("otherTableFiltersToDelete");
|
||||
ListingHash<String, QRecord> otherTableRecordsToStore = (ListingHash<String, QRecord>) runBackendStepInput.getValue("otherTableRecordsToStore");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ListingHash<String, QRecord> otherTableRecordsToStore = (ListingHash<String, QRecord>) runBackendStepInput.getValue("otherTableRecordsToStore");
|
||||
|
||||
if(otherTableIdsToDelete != null)
|
||||
{
|
||||
|
@ -31,6 +31,7 @@ import java.time.format.DateTimeFormatter;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.GenerateReportAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
@ -58,7 +59,7 @@ public class ExecuteReportStep implements BackendStep
|
||||
try
|
||||
{
|
||||
String reportName = runBackendStepInput.getValueString("reportName");
|
||||
QReportMetaData report = runBackendStepInput.getInstance().getReport(reportName);
|
||||
QReportMetaData report = QContext.getQInstance().getReport(reportName);
|
||||
File tmpFile = File.createTempFile(reportName, ".xlsx", new File("/tmp/"));
|
||||
|
||||
runBackendStepInput.getAsyncJobCallback().updateStatus("Generating Report");
|
||||
|
@ -26,6 +26,7 @@ import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
@ -106,15 +107,16 @@ public class PrepareReportForRecordStep extends PrepareReportStep
|
||||
}
|
||||
|
||||
String reportName = runBackendStepInput.getValueString("reportName");
|
||||
QReportMetaData report = runBackendStepInput.getInstance().getReport(reportName);
|
||||
QReportMetaData report = QContext.getQInstance().getReport(reportName);
|
||||
// runBackendStepOutput.addValue("downloadFileBaseName", runBackendStepInput.getTable().getLabel() + " " + record.getRecordLabel());
|
||||
runBackendStepOutput.addValue("downloadFileBaseName", report.getLabel() + " - " + record.getRecordLabel());
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// if there are no more input fields, then remove the INPUT step from the process. //
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
inputFieldList = (ArrayList<QFieldMetaData>) runBackendStepOutput.getValue("inputFieldList");
|
||||
if(!CollectionUtils.nullSafeHasContents(inputFieldList))
|
||||
@SuppressWarnings("unchecked")
|
||||
ArrayList<QFieldMetaData> updatedInputFieldList = (ArrayList<QFieldMetaData>) runBackendStepOutput.getValue("inputFieldList");
|
||||
if(!CollectionUtils.nullSafeHasContents(updatedInputFieldList))
|
||||
{
|
||||
removeInputStepFromProcess(runBackendStepOutput);
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.processes.implementations.reports;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
@ -56,7 +57,7 @@ public class PrepareReportStep implements BackendStep
|
||||
throw (new QException("Process value [reportName] was not given."));
|
||||
}
|
||||
|
||||
QReportMetaData report = runBackendStepInput.getInstance().getReport(reportName);
|
||||
QReportMetaData report = QContext.getQInstance().getReport(reportName);
|
||||
if(report == null)
|
||||
{
|
||||
throw (new QException("Process named [" + reportName + "] was not found in this instance."));
|
||||
|
@ -129,6 +129,7 @@ public class RenderSavedReportExecuteStep implements BackendStep
|
||||
.withRenderedReportStatusId(RenderedReportStatus.RUNNING.getId())
|
||||
.withReportFormat(ReportFormatPossibleValueEnum.valueOf(reportFormat.name()).getPossibleValueId())
|
||||
)).getRecords().get(0);
|
||||
runBackendStepOutput.addValue("renderedReportId", renderedReportRecord.getValue("id"));
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// convert the report record to report meta-data, which the GenerateReportAction works on //
|
||||
|
@ -0,0 +1,258 @@
|
||||
/*
|
||||
* 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.processes.implementations.savedreports;
|
||||
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
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.Capability;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.savedreports.RenderedReport;
|
||||
import com.kingsrook.qqq.backend.core.model.savedreports.ReportColumns;
|
||||
import com.kingsrook.qqq.backend.core.model.savedreports.SavedReport;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.Pair;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility for verifying that the RenderReports process works for all fields,
|
||||
** on all tables, and all exposed joins.
|
||||
**
|
||||
** Meant for use within a unit test, or maybe as part of an instance's boot-up/
|
||||
** validation.
|
||||
*******************************************************************************/
|
||||
public class ReportsFullInstanceVerifier
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(ReportsFullInstanceVerifier.class);
|
||||
|
||||
private boolean removeRenderedReports = true;
|
||||
private boolean filterForAtMostOneRowPerReport = true;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void verify(Collection<QTableMetaData> tables, String storageTableName) throws QException
|
||||
{
|
||||
Map<Pair<String, String>, Exception> caughtExceptions = new LinkedHashMap<>();
|
||||
for(QTableMetaData table : tables)
|
||||
{
|
||||
if(table.isCapabilityEnabled(QContext.getQInstance().getBackendForTable(table.getName()), Capability.TABLE_QUERY))
|
||||
{
|
||||
LOG.info("Verifying Reports on table", logPair("tableName", table.getName()));
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// run the table by itself (no join fields) //
|
||||
//////////////////////////////////////////////
|
||||
runReport(table.getName(), Collections.emptyList(), "main-table-only", caughtExceptions, storageTableName);
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
// run once w/ the fields from each exposed join //
|
||||
///////////////////////////////////////////////////
|
||||
for(ExposedJoin exposedJoin : CollectionUtils.nonNullList(table.getExposedJoins()))
|
||||
{
|
||||
runReport(table.getName(), List.of(exposedJoin), "join-" + exposedJoin.getLabel(), caughtExceptions, storageTableName);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// run w/ all exposed joins (if there are any) //
|
||||
/////////////////////////////////////////////////
|
||||
if(CollectionUtils.nullSafeHasContents(table.getExposedJoins()))
|
||||
{
|
||||
runReport(table.getName(), table.getExposedJoins(), "all-joins", caughtExceptions, storageTableName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// log out an exceptions caught //
|
||||
//////////////////////////////////
|
||||
if(!caughtExceptions.isEmpty())
|
||||
{
|
||||
for(Map.Entry<Pair<String, String>, Exception> entry : caughtExceptions.entrySet())
|
||||
{
|
||||
LOG.info("Caught an exception verifying reports", entry.getValue(), logPair("tableName", entry.getKey().getA()), logPair("fieldName", entry.getKey().getB()));
|
||||
}
|
||||
throw (new QException("Reports Verification failed with " + caughtExceptions.size() + " exception" + StringUtils.plural(caughtExceptions.size())));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void runReport(String tableName, List<ExposedJoin> exposedJoinList, String description, Map<Pair<String, String>, Exception> caughtExceptions, String storageTableName)
|
||||
{
|
||||
try
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// build the list of reports to include in the column - starting with all fields in the table //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
ReportColumns reportColumns = new ReportColumns();
|
||||
for(QFieldMetaData field : QContext.getQInstance().getTable(tableName).getFields().values())
|
||||
{
|
||||
reportColumns.withColumn(field.getName());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
// add all fields from all exposed joins as well //
|
||||
///////////////////////////////////////////////////
|
||||
for(ExposedJoin exposedJoin : CollectionUtils.nonNullList(exposedJoinList))
|
||||
{
|
||||
QTableMetaData joinTable = QContext.getQInstance().getTable(exposedJoin.getJoinTable());
|
||||
for(QFieldMetaData field : joinTable.getFields().values())
|
||||
{
|
||||
reportColumns.withColumn(joinTable.getName() + "." + field.getName());
|
||||
}
|
||||
}
|
||||
|
||||
QQueryFilter queryFilter = new QQueryFilter();
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if caller is okay with a filter that should limit the report to a small number of rows (could be more than 1 for to-many joins), then do so //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(filterForAtMostOneRowPerReport)
|
||||
{
|
||||
queryFilter.withCriteria(QContext.getQInstance().getTable(tableName).getPrimaryKeyField(), QCriteriaOperator.EQUALS, 1);
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// insert a saved report record //
|
||||
//////////////////////////////////
|
||||
SavedReport savedReport = new SavedReport();
|
||||
savedReport.setTableName(tableName);
|
||||
savedReport.setLabel("Test " + tableName + " " + description);
|
||||
savedReport.setColumnsJson(JsonUtils.toJson(reportColumns));
|
||||
savedReport.setQueryFilterJson(JsonUtils.toJson(queryFilter));
|
||||
List<QRecord> reportRecordList = new InsertAction().execute(new InsertInput(SavedReport.TABLE_NAME).withRecordEntity(savedReport)).getRecords();
|
||||
|
||||
///////////////////////
|
||||
// render the report //
|
||||
///////////////////////
|
||||
RunBackendStepInput input = new RunBackendStepInput();
|
||||
RunBackendStepOutput output = new RunBackendStepOutput();
|
||||
|
||||
input.addValue(RenderSavedReportMetaDataProducer.FIELD_NAME_REPORT_FORMAT, ReportFormat.CSV.name());
|
||||
input.addValue(RenderSavedReportMetaDataProducer.FIELD_NAME_STORAGE_TABLE_NAME, storageTableName);
|
||||
input.setRecords(reportRecordList);
|
||||
|
||||
new RenderSavedReportExecuteStep().run(input, output);
|
||||
|
||||
//////////////////////////////////////////
|
||||
// clean up the report, if so requested //
|
||||
//////////////////////////////////////////
|
||||
if(removeRenderedReports)
|
||||
{
|
||||
new DeleteAction().execute(new DeleteInput(RenderedReport.TABLE_NAME).withPrimaryKey(output.getValue("renderedReportId")));
|
||||
}
|
||||
}
|
||||
catch(QException e)
|
||||
{
|
||||
caughtExceptions.put(Pair.of(tableName, description), e);
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for removeRenderedReports
|
||||
*******************************************************************************/
|
||||
public boolean getRemoveRenderedReports()
|
||||
{
|
||||
return (this.removeRenderedReports);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for removeRenderedReports
|
||||
*******************************************************************************/
|
||||
public void setRemoveRenderedReports(boolean removeRenderedReports)
|
||||
{
|
||||
this.removeRenderedReports = removeRenderedReports;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for removeRenderedReports
|
||||
*******************************************************************************/
|
||||
public ReportsFullInstanceVerifier withRemoveRenderedReports(boolean removeRenderedReports)
|
||||
{
|
||||
this.removeRenderedReports = removeRenderedReports;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for filterForAtMostOneRowPerReport
|
||||
*******************************************************************************/
|
||||
public boolean getFilterForAtMostOneRowPerReport()
|
||||
{
|
||||
return (this.filterForAtMostOneRowPerReport);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for filterForAtMostOneRowPerReport
|
||||
*******************************************************************************/
|
||||
public void setFilterForAtMostOneRowPerReport(boolean filterForAtMostOneRowPerReport)
|
||||
{
|
||||
this.filterForAtMostOneRowPerReport = filterForAtMostOneRowPerReport;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for filterForAtMostOneRowPerReport
|
||||
*******************************************************************************/
|
||||
public ReportsFullInstanceVerifier withFilterForAtMostOneRowPerReport(boolean filterForAtMostOneRowPerReport)
|
||||
{
|
||||
this.filterForAtMostOneRowPerReport = filterForAtMostOneRowPerReport;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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.processes.implementations.savedreports;
|
||||
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.savedreports.RenderedReport;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility for verifying that the RenderReports process works for all report
|
||||
** records stored in the saved reports table.
|
||||
**
|
||||
** Meant for use within a unit test, or maybe as part of an instance's boot-up/
|
||||
** validation.
|
||||
*******************************************************************************/
|
||||
public class SavedReportsTableFullVerifier
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(SavedReportsTableFullVerifier.class);
|
||||
|
||||
private boolean removeRenderedReports = true;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void verify(List<QRecord> savedReportRecordList, String storageTableName) throws QException
|
||||
{
|
||||
Map<Integer, Exception> caughtExceptions = new LinkedHashMap<>();
|
||||
for(QRecord report : savedReportRecordList)
|
||||
{
|
||||
runReport(report, caughtExceptions, storageTableName);
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// log out an exceptions caught //
|
||||
//////////////////////////////////
|
||||
if(!caughtExceptions.isEmpty())
|
||||
{
|
||||
for(Map.Entry<Integer, Exception> entry : caughtExceptions.entrySet())
|
||||
{
|
||||
LOG.info("Caught an exception verifying saved reports", entry.getValue(), logPair("savdReportId", entry.getKey()));
|
||||
}
|
||||
throw (new QException("Saved Reports Verification failed with " + caughtExceptions.size() + " exception" + StringUtils.plural(caughtExceptions.size())));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void runReport(QRecord savedReport, Map<Integer, Exception> caughtExceptions, String storageTableName)
|
||||
{
|
||||
try
|
||||
{
|
||||
///////////////////////
|
||||
// render the report //
|
||||
///////////////////////
|
||||
RunBackendStepInput input = new RunBackendStepInput();
|
||||
RunBackendStepOutput output = new RunBackendStepOutput();
|
||||
|
||||
input.addValue(RenderSavedReportMetaDataProducer.FIELD_NAME_REPORT_FORMAT, ReportFormat.XLSX.name());
|
||||
input.addValue(RenderSavedReportMetaDataProducer.FIELD_NAME_STORAGE_TABLE_NAME, storageTableName);
|
||||
input.setRecords(List.of(savedReport));
|
||||
|
||||
new RenderSavedReportExecuteStep().run(input, output);
|
||||
Exception exception = output.getException();
|
||||
if(exception != null)
|
||||
{
|
||||
throw (exception);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
// clean up the report, if so requested //
|
||||
//////////////////////////////////////////
|
||||
if(removeRenderedReports)
|
||||
{
|
||||
new DeleteAction().execute(new DeleteInput(RenderedReport.TABLE_NAME).withPrimaryKey(output.getValue("renderedReportId")));
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
caughtExceptions.put(savedReport.getValueInteger("id"), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for removeRenderedReports
|
||||
*******************************************************************************/
|
||||
public boolean getRemoveRenderedReports()
|
||||
{
|
||||
return (this.removeRenderedReports);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for removeRenderedReports
|
||||
*******************************************************************************/
|
||||
public void setRemoveRenderedReports(boolean removeRenderedReports)
|
||||
{
|
||||
this.removeRenderedReports = removeRenderedReports;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for removeRenderedReports
|
||||
*******************************************************************************/
|
||||
public SavedReportsTableFullVerifier withRemoveRenderedReports(boolean removeRenderedReports)
|
||||
{
|
||||
this.removeRenderedReports = removeRenderedReports;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -29,6 +29,7 @@ import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
@ -44,6 +45,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.savedviews.SavedView;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -78,10 +80,10 @@ public class QuerySavedViewProcess implements BackendStep
|
||||
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
|
||||
{
|
||||
ActionHelper.validateSession(runBackendStepInput);
|
||||
Integer savedViewId = runBackendStepInput.getValueInteger("id");
|
||||
|
||||
try
|
||||
{
|
||||
Integer savedViewId = runBackendStepInput.getValueInteger("id");
|
||||
if(savedViewId != null)
|
||||
{
|
||||
GetInput input = new GetInput();
|
||||
@ -89,6 +91,11 @@ public class QuerySavedViewProcess implements BackendStep
|
||||
input.setPrimaryKey(savedViewId);
|
||||
|
||||
GetOutput output = new GetAction().execute(input);
|
||||
if(output.getRecord() == null)
|
||||
{
|
||||
throw (new QNotFoundException("The requested view was not found."));
|
||||
}
|
||||
|
||||
runBackendStepOutput.addRecord(output.getRecord());
|
||||
runBackendStepOutput.addValue("savedView", output.getRecord());
|
||||
runBackendStepOutput.addValue("savedViewList", (Serializable) List.of(output.getRecord()));
|
||||
@ -108,6 +115,11 @@ public class QuerySavedViewProcess implements BackendStep
|
||||
runBackendStepOutput.addValue("savedViewList", (Serializable) output.getRecords());
|
||||
}
|
||||
}
|
||||
catch(QNotFoundException qnfe)
|
||||
{
|
||||
LOG.info("View not found", logPair("savedViewId", savedViewId));
|
||||
throw (qnfe);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error querying for saved views", e);
|
||||
|
@ -145,7 +145,7 @@ public class StoreScriptRevisionProcessStep implements BackendStep
|
||||
|
||||
try
|
||||
{
|
||||
scriptRevision.setValue("author", input.getSession().getUser().getFullName());
|
||||
scriptRevision.setValue("author", QContext.getQSession().getUser().getFullName());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
|
@ -139,7 +139,7 @@ public class TestScriptProcessStep implements BackendStep
|
||||
//////////////////////////////////
|
||||
// send script outputs back out //
|
||||
//////////////////////////////////
|
||||
output.addValue("scriptLogLines", CollectionUtils.useOrWrap(testScriptOutput.getScriptLogLines(), TypeToken.get(ArrayList.class)));
|
||||
output.addValue("scriptLogLines", CollectionUtils.useOrWrap(testScriptOutput.getScriptLogLines(), new TypeToken<ArrayList<QRecord>>() {}));
|
||||
output.addValue("outputObject", testScriptOutput.getOutputObject());
|
||||
|
||||
if(testScriptOutput.getException() != null)
|
||||
|
@ -365,10 +365,10 @@ public abstract class AbstractTableSyncTransformStep extends AbstractTransformSt
|
||||
{
|
||||
if(possibleValueTranslator == null)
|
||||
{
|
||||
possibleValueTranslator = new QPossibleValueTranslator(runBackendStepInput.getInstance(), runBackendStepInput.getSession());
|
||||
possibleValueTranslator = new QPossibleValueTranslator(QContext.getQInstance(), QContext.getQSession());
|
||||
}
|
||||
|
||||
possibleValueTranslator.translatePossibleValuesInRecords(runBackendStepInput.getInstance().getTable(destinationTableName), runBackendStepOutput.getRecords());
|
||||
possibleValueTranslator.translatePossibleValuesInRecords(QContext.getQInstance().getTable(destinationTableName), runBackendStepOutput.getRecords());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -433,6 +433,8 @@ public class ProcessLockUtils
|
||||
{
|
||||
throw (new QException("Error deleting processLock record: " + deleteOutput.getRecordsWithErrors().get(0).getErrorsAsString()));
|
||||
}
|
||||
|
||||
LOG.info("Released process lock", logPair("id", processLock.getId()), logPair("key", processLock.getKey()), logPair("typeId", processLock.getProcessLockTypeId()), logPair("details", processLock.getDetails()));
|
||||
}
|
||||
catch(QException e)
|
||||
{
|
||||
|
@ -32,6 +32,7 @@ import java.util.function.Consumer;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
||||
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.exceptions.QUserFacingException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
@ -515,7 +516,7 @@ public class GeneralProcessUtils
|
||||
*******************************************************************************/
|
||||
public static Integer validateSingleSelectedId(RunBackendStepInput runBackendStepInput, String tableName) throws QException
|
||||
{
|
||||
String tableLabel = runBackendStepInput.getInstance().getTable(tableName).getLabel();
|
||||
String tableLabel = QContext.getQInstance().getTable(tableName).getLabel();
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// Get the selected recordId and verify we only 1 //
|
||||
|
@ -70,7 +70,9 @@ public class QuartzJobRunner implements Job
|
||||
QContext.init(qInstance, quartzScheduler.getSessionSupplier().get());
|
||||
|
||||
schedulableType = qInstance.getSchedulableType(context.getJobDetail().getJobDataMap().getString("type"));
|
||||
params = (Map<String, Object>) context.getJobDetail().getJobDataMap().get("params");
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> paramsFromJobDataMap = (Map<String, Object>) context.getJobDetail().getJobDataMap().get("params");
|
||||
params = paramsFromJobDataMap;
|
||||
|
||||
SchedulableRunner schedulableRunner = QCodeLoader.getAdHoc(SchedulableRunner.class, schedulableType.getRunner());
|
||||
|
||||
|
@ -74,7 +74,9 @@ public class SchedulableProcessRunner implements SchedulableRunner
|
||||
Map<String, Serializable> backendVariantData = null;
|
||||
if(params.containsKey("backendVariantData"))
|
||||
{
|
||||
backendVariantData = (Map<String, Serializable>) params.get("backendVariantData");
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Serializable> dataFromMap = (Map<String, Serializable>) params.get("backendVariantData");
|
||||
backendVariantData = dataFromMap;
|
||||
}
|
||||
|
||||
Map<String, Serializable> processInputValues = buildProcessInputValuesMap(params, process);
|
||||
|
@ -26,6 +26,7 @@ import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
@ -78,7 +79,7 @@ public class TempFileStateProvider implements StateProviderInterface
|
||||
try
|
||||
{
|
||||
String json = JsonUtils.toJson(data);
|
||||
FileUtils.writeStringToFile(getFile(key), json);
|
||||
FileUtils.writeStringToFile(getFile(key), json, StandardCharsets.UTF_8);
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
@ -97,7 +98,7 @@ public class TempFileStateProvider implements StateProviderInterface
|
||||
{
|
||||
try
|
||||
{
|
||||
String json = FileUtils.readFileToString(getFile(key));
|
||||
String json = FileUtils.readFileToString(getFile(key), StandardCharsets.UTF_8);
|
||||
return (Optional.of(JsonUtils.toObject(json, type)));
|
||||
}
|
||||
catch(FileNotFoundException | NoSuchFileException fnfe)
|
||||
|
@ -50,7 +50,7 @@ public class CollectionUtils
|
||||
** true if c is null or it's empty
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static boolean nullSafeIsEmpty(Collection c)
|
||||
public static boolean nullSafeIsEmpty(Collection<?> c)
|
||||
{
|
||||
if(c == null || c.isEmpty())
|
||||
{
|
||||
@ -66,7 +66,7 @@ public class CollectionUtils
|
||||
** true if c is null or it's empty
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static boolean nullSafeIsEmpty(Map c)
|
||||
public static boolean nullSafeIsEmpty(Map<?, ?> c)
|
||||
{
|
||||
if(c == null || c.isEmpty())
|
||||
{
|
||||
@ -82,7 +82,7 @@ public class CollectionUtils
|
||||
** true if c is NOT null and it's not empty
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static boolean nullSafeHasContents(Collection c)
|
||||
public static boolean nullSafeHasContents(Collection<?> c)
|
||||
{
|
||||
return (!nullSafeIsEmpty(c));
|
||||
}
|
||||
@ -93,7 +93,7 @@ public class CollectionUtils
|
||||
** true if c is NOT null and it's not empty
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static boolean nullSafeHasContents(Map c)
|
||||
public static boolean nullSafeHasContents(Map<?, ?> c)
|
||||
{
|
||||
return (!nullSafeIsEmpty(c));
|
||||
}
|
||||
@ -104,7 +104,7 @@ public class CollectionUtils
|
||||
** 0 if c is empty, otherwise, its size.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static int nullSafeSize(Collection c)
|
||||
public static int nullSafeSize(Collection<?> c)
|
||||
{
|
||||
if(c == null)
|
||||
{
|
||||
@ -120,7 +120,7 @@ public class CollectionUtils
|
||||
** 0 if c is empty, otherwise, its size.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static int nullSafeSize(Map c)
|
||||
public static int nullSafeSize(Map<?, ?> c)
|
||||
{
|
||||
if(c == null)
|
||||
{
|
||||
@ -302,14 +302,14 @@ public class CollectionUtils
|
||||
return (rs);
|
||||
}
|
||||
|
||||
List<T> currentPage = new LinkedList<T>();
|
||||
List<T> currentPage = new LinkedList<>();
|
||||
rs.add(currentPage);
|
||||
|
||||
for(T value : values)
|
||||
{
|
||||
if(currentPage.size() >= pageSize)
|
||||
{
|
||||
currentPage = new LinkedList<T>();
|
||||
currentPage = new LinkedList<>();
|
||||
rs.add(currentPage);
|
||||
}
|
||||
|
||||
@ -423,6 +423,7 @@ public class CollectionUtils
|
||||
**
|
||||
** Meant to help avoid null checks on foreach loops.
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T[] nonNullArray(T[] array)
|
||||
{
|
||||
if(array == null)
|
||||
@ -539,7 +540,7 @@ public class CollectionUtils
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static Map objectToMap(Object o)
|
||||
public static Map<?, ?> objectToMap(Object o)
|
||||
{
|
||||
ObjectMapper mapper = new ObjectMapper()
|
||||
.registerModule(new JavaTimeModule())
|
||||
@ -555,6 +556,7 @@ public class CollectionUtils
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SafeVarargs
|
||||
public static <T> List<T> mergeLists(List<T>... lists)
|
||||
{
|
||||
List<T> rs = new ArrayList<>();
|
||||
@ -593,6 +595,7 @@ public class CollectionUtils
|
||||
return (null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<T> targetClass = (Class<T>) typeToken.getRawType();
|
||||
if(targetClass.isInstance(collection))
|
||||
{
|
||||
@ -630,6 +633,7 @@ public class CollectionUtils
|
||||
return (null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<T> targetClass = (Class<T>) typeToken.getRawType();
|
||||
if(targetClass.isInstance(collection))
|
||||
{
|
||||
|
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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.utils;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.MutableMap;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Hash that provides "counting" capability -- keys map to Integers that
|
||||
** are automatically/easily summed to
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class CountingHash<K extends Serializable> extends AbstractMap<K, Integer> implements Serializable
|
||||
{
|
||||
private Map<K, Integer> map = null;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Default constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public CountingHash()
|
||||
{
|
||||
this.map = new HashMap<>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor where you can supply a source map (e.g., if you want a specific
|
||||
** Map type (like LinkedHashMap), or with pre-values.
|
||||
**
|
||||
** Note - the input map will be wrapped in a MutableMap - so - it'll be mutable.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public CountingHash(Map<K, Integer> sourceMap)
|
||||
{
|
||||
this.map = new MutableMap<>(sourceMap);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Increment the value for the specified key by 1.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Integer add(K key)
|
||||
{
|
||||
Integer value = getOrCreateListForKey(key);
|
||||
Integer sum = value + 1;
|
||||
map.put(key, sum);
|
||||
return (sum);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Increment the value for the specified key by the supplied addend
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Integer add(K key, Integer addend)
|
||||
{
|
||||
Integer value = getOrCreateListForKey(key);
|
||||
Integer sum = value + addend;
|
||||
map.put(key, sum);
|
||||
return (sum);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Integer getOrCreateListForKey(K key)
|
||||
{
|
||||
Integer value;
|
||||
|
||||
if(!this.map.containsKey(key))
|
||||
{
|
||||
this.map.put(key, 0);
|
||||
value = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = this.map.get(key);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
public Set<Entry<K, Integer>> entrySet()
|
||||
{
|
||||
return this.map.entrySet();
|
||||
}
|
||||
|
||||
}
|
@ -37,6 +37,7 @@ public class ObjectUtils
|
||||
/*******************************************************************************
|
||||
** A varargs version of Objects.requireNonNullElse
|
||||
*******************************************************************************/
|
||||
@SafeVarargs
|
||||
public static <T> T requireNonNullElse(T... objects)
|
||||
{
|
||||
if(objects == null)
|
||||
|
@ -888,6 +888,7 @@ public class ValueUtils
|
||||
** Return the first argument that isn't null.
|
||||
** If all were null, return null.
|
||||
*******************************************************************************/
|
||||
@SafeVarargs
|
||||
public static <T> T getFirstNonNull(T... ts)
|
||||
{
|
||||
if(ts == null || ts.length == 0)
|
||||
|
@ -48,7 +48,11 @@ public class YamlUtils
|
||||
{
|
||||
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
|
||||
objectMapper.findAndRegisterModules();
|
||||
return (objectMapper.readValue(yaml, Map.class));
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> map = objectMapper.readValue(yaml, Map.class);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
|
@ -86,7 +86,7 @@ public class AlphaNumericComparator implements Comparator<String>
|
||||
////////////////////////////////////////////////////////////////
|
||||
if(INT_PATTERN.matcher(a).matches() && INT_PATTERN.matcher(b).matches())
|
||||
{
|
||||
int intsCompared = new Integer(a).compareTo(new Integer(b));
|
||||
int intsCompared = Integer.valueOf(a).compareTo(Integer.valueOf(b));
|
||||
if(intsCompared == TIE)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@ -119,7 +119,7 @@ public class AlphaNumericComparator implements Comparator<String>
|
||||
/////////////////////////////////////////////////////////////
|
||||
// if the ints compare as non-zero, return that comparison //
|
||||
/////////////////////////////////////////////////////////////
|
||||
int intPartCompared = new Integer(aIntPart).compareTo(new Integer(bIntPart));
|
||||
int intPartCompared = Integer.valueOf(aIntPart).compareTo(Integer.valueOf(bIntPart));
|
||||
if(intPartCompared != TIE)
|
||||
{
|
||||
return (intPartCompared);
|
||||
|
@ -26,7 +26,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@SuppressWarnings({ "checkstyle:javadoc", "DanglingJavadoc" })
|
||||
/*******************************************************************************
|
||||
** List.of is "great", but annoying because it makes unmodifiable lists...
|
||||
** So, replace it with this, which returns ArrayLists, which "don't suck"
|
||||
|
@ -27,7 +27,6 @@ import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
||||
@SuppressWarnings({ "checkstyle:javadoc", "DanglingJavadoc" })
|
||||
/*******************************************************************************
|
||||
** Map.of is "great", but annoying because it makes unmodifiable maps, and it
|
||||
** NPE's on nulls... So, replace it with this, which returns HashMaps (or maps
|
||||
|
@ -87,7 +87,9 @@ public class MultiLevelMapHelper
|
||||
{
|
||||
try
|
||||
{
|
||||
return (map.getClass().getConstructor().newInstance());
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<K2, V> map1 = map.getClass().getConstructor().newInstance();
|
||||
return map1;
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
|
@ -174,7 +174,7 @@ class StandardScheduledExecutorTest extends BaseTest
|
||||
@Override
|
||||
public void execute(RecordAutomationInput recordAutomationInput) throws QException
|
||||
{
|
||||
sessionId = recordAutomationInput.getSession().getIdReference();
|
||||
sessionId = QContext.getQSession().getIdReference();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ class ChildRecordListRendererTest extends BaseTest
|
||||
.getWidgetMetaData();
|
||||
qInstance.addWidget(widget);
|
||||
|
||||
TestUtils.insertRecords(qInstance, qInstance.getTable(TestUtils.TABLE_NAME_ORDER), List.of(
|
||||
TestUtils.insertRecords(qInstance.getTable(TestUtils.TABLE_NAME_ORDER), List.of(
|
||||
new QRecord().withValue("id", 1)
|
||||
));
|
||||
|
||||
@ -130,12 +130,12 @@ class ChildRecordListRendererTest extends BaseTest
|
||||
.getWidgetMetaData();
|
||||
qInstance.addWidget(widget);
|
||||
|
||||
TestUtils.insertRecords(qInstance, qInstance.getTable(TestUtils.TABLE_NAME_ORDER), List.of(
|
||||
TestUtils.insertRecords(qInstance.getTable(TestUtils.TABLE_NAME_ORDER), List.of(
|
||||
new QRecord().withValue("id", 1),
|
||||
new QRecord().withValue("id", 2)
|
||||
));
|
||||
|
||||
TestUtils.insertRecords(qInstance, qInstance.getTable(TestUtils.TABLE_NAME_LINE_ITEM), List.of(
|
||||
TestUtils.insertRecords(qInstance.getTable(TestUtils.TABLE_NAME_LINE_ITEM), List.of(
|
||||
new QRecord().withValue("orderId", 1).withValue("sku", "ABC").withValue("lineNumber", 2),
|
||||
new QRecord().withValue("orderId", 1).withValue("sku", "BCD").withValue("lineNumber", 1),
|
||||
new QRecord().withValue("orderId", 2).withValue("sku", "XYZ") // should not be found.
|
||||
|
@ -128,7 +128,7 @@ class ParentWidgetRendererTest extends BaseTest
|
||||
|
||||
QContext.getQSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_TYPE_STORE_ALL_ACCESS, true);
|
||||
|
||||
TestUtils.insertRecords(qInstance, qInstance.getTable(TestUtils.TABLE_NAME_ORDER), List.of(
|
||||
TestUtils.insertRecords(qInstance.getTable(TestUtils.TABLE_NAME_ORDER), List.of(
|
||||
new QRecord().withValue("id", 1)
|
||||
));
|
||||
|
||||
@ -161,12 +161,12 @@ class ParentWidgetRendererTest extends BaseTest
|
||||
|
||||
QContext.getQSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_TYPE_STORE_ALL_ACCESS, true);
|
||||
|
||||
TestUtils.insertRecords(qInstance, qInstance.getTable(TestUtils.TABLE_NAME_ORDER), List.of(
|
||||
TestUtils.insertRecords(qInstance.getTable(TestUtils.TABLE_NAME_ORDER), List.of(
|
||||
new QRecord().withValue("id", 1),
|
||||
new QRecord().withValue("id", 2)
|
||||
));
|
||||
|
||||
TestUtils.insertRecords(qInstance, qInstance.getTable(TestUtils.TABLE_NAME_LINE_ITEM), List.of(
|
||||
TestUtils.insertRecords(qInstance.getTable(TestUtils.TABLE_NAME_LINE_ITEM), List.of(
|
||||
new QRecord().withValue("orderId", 1).withValue("sku", "ABC").withValue("lineNumber", 2),
|
||||
new QRecord().withValue("orderId", 1).withValue("sku", "BCD").withValue("lineNumber", 1),
|
||||
new QRecord().withValue("orderId", 2).withValue("sku", "XYZ") // should not be found.
|
||||
|
@ -109,7 +109,7 @@ class ProcessWidgetRendererTest extends BaseTest
|
||||
.getWidgetMetaData();
|
||||
qInstance.addWidget(widget);
|
||||
|
||||
TestUtils.insertRecords(qInstance, qInstance.getTable(TestUtils.TABLE_NAME_ORDER), List.of(
|
||||
TestUtils.insertRecords(qInstance.getTable(TestUtils.TABLE_NAME_ORDER), List.of(
|
||||
new QRecord().withValue("id", 1)
|
||||
));
|
||||
|
||||
@ -140,12 +140,12 @@ class ProcessWidgetRendererTest extends BaseTest
|
||||
.getWidgetMetaData();
|
||||
qInstance.addWidget(widget);
|
||||
|
||||
TestUtils.insertRecords(qInstance, qInstance.getTable(TestUtils.TABLE_NAME_ORDER), List.of(
|
||||
TestUtils.insertRecords(qInstance.getTable(TestUtils.TABLE_NAME_ORDER), List.of(
|
||||
new QRecord().withValue("id", 1),
|
||||
new QRecord().withValue("id", 2)
|
||||
));
|
||||
|
||||
TestUtils.insertRecords(qInstance, qInstance.getTable(TestUtils.TABLE_NAME_LINE_ITEM), List.of(
|
||||
TestUtils.insertRecords(qInstance.getTable(TestUtils.TABLE_NAME_LINE_ITEM), List.of(
|
||||
new QRecord().withValue("orderId", 1).withValue("sku", "ABC").withValue("lineNumber", 2),
|
||||
new QRecord().withValue("orderId", 1).withValue("sku", "BCD").withValue("lineNumber", 1),
|
||||
new QRecord().withValue("orderId", 2).withValue("sku", "XYZ") // should not be found.
|
||||
|
@ -1,5 +1,22 @@
|
||||
/*
|
||||
* Copyright © 2022-2023. ColdTrack <contact@coldtrack.com>. All Rights Reserved.
|
||||
* 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.dashboard.widgets;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user