mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-17 20:50:44 +00:00
Merged dev into feature/dk-misc-20250327
This commit is contained in:
@ -649,7 +649,7 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
input.setRecordList(records);
|
||||
input.setAction(action);
|
||||
|
||||
RecordAutomationHandler recordAutomationHandler = QCodeLoader.getRecordAutomationHandler(action);
|
||||
RecordAutomationHandler recordAutomationHandler = QCodeLoader.getAdHoc(RecordAutomationHandler.class, action.getCodeReference());
|
||||
recordAutomationHandler.execute(input);
|
||||
}
|
||||
}
|
||||
|
@ -24,17 +24,11 @@ package com.kingsrook.qqq.backend.core.actions.customizers;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.InitializableViaCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction;
|
||||
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeFunction;
|
||||
import com.kingsrook.qqq.backend.core.utils.memoization.Memoization;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
@ -43,9 +37,7 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
/*******************************************************************************
|
||||
** Utility to load code for running QQQ customizers.
|
||||
**
|
||||
** TODO - redo all to go through method that memoizes class & constructor
|
||||
** lookup. That memoziation causes 1,000,000 such calls to go from ~500ms
|
||||
** to ~100ms.
|
||||
** That memoization causes 1,000,000 such calls to go from ~500ms to ~100ms.
|
||||
*******************************************************************************/
|
||||
public class QCodeLoader
|
||||
{
|
||||
@ -70,84 +62,6 @@ public class QCodeLoader
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T, R> Function<T, R> getFunction(QCodeReference codeReference)
|
||||
{
|
||||
if(codeReference == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
if(!codeReference.getCodeType().equals(QCodeType.JAVA))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// todo - 1) support more languages, 2) wrap them w/ java Functions here, 3) profit! //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
throw (new IllegalArgumentException("Only JAVA customizers are supported at this time."));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Class<?> customizerClass = Class.forName(codeReference.getName());
|
||||
return ((Function<T, R>) customizerClass.getConstructor().newInstance());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error initializing customizer", e, logPair("codeReference", codeReference));
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// return null here - under the assumption that during normal run-time operations, we'll never hit here //
|
||||
// as we'll want to validate all functions in the instance validator at startup time (and IT will throw //
|
||||
// if it finds an invalid code reference //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
return (null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends BackendStep> T getBackendStep(Class<T> expectedType, QCodeReference codeReference)
|
||||
{
|
||||
if(codeReference == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
if(!codeReference.getCodeType().equals(QCodeType.JAVA))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// todo - 1) support more languages, 2) wrap them w/ java Functions here, 3) profit! //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
throw (new IllegalArgumentException("Only JAVA BackendSteps are supported at this time."));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Class<?> customizerClass = Class.forName(codeReference.getName());
|
||||
return ((T) customizerClass.getConstructor().newInstance());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error initializing customizer", e, logPair("codeReference", codeReference));
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// return null here - under the assumption that during normal run-time operations, we'll never hit here //
|
||||
// as we'll want to validate all functions in the instance validator at startup time (and IT will throw //
|
||||
// if it finds an invalid code reference //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
return (null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -177,7 +91,17 @@ public class QCodeLoader
|
||||
|
||||
if(constructor.isPresent())
|
||||
{
|
||||
return ((T) constructor.get().newInstance());
|
||||
T t = (T) constructor.get().newInstance();
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// if the object is initializable, then, well, initialize it! //
|
||||
////////////////////////////////////////////////////////////////
|
||||
if(t instanceof InitializableViaCodeReference initializableViaCodeReference)
|
||||
{
|
||||
initializableViaCodeReference.initialize(codeReference);
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -187,7 +111,7 @@ public class QCodeLoader
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error initializing customizer", e, logPair("codeReference", codeReference));
|
||||
LOG.error("Error initializing codeReference", e, logPair("codeReference", codeReference));
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// return null here - under the assumption that during normal run-time operations, we'll never hit here //
|
||||
@ -198,67 +122,4 @@ public class QCodeLoader
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static RecordAutomationHandler getRecordAutomationHandler(TableAutomationAction action) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
QCodeReference codeReference = action.getCodeReference();
|
||||
if(!codeReference.getCodeType().equals(QCodeType.JAVA))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// todo - 1) support more languages, 2) wrap them w/ java Functions here, 3) profit! //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
throw (new IllegalArgumentException("Only JAVA customizers are supported at this time."));
|
||||
}
|
||||
|
||||
Class<?> codeClass = Class.forName(codeReference.getName());
|
||||
Object codeObject = codeClass.getConstructor().newInstance();
|
||||
if(!(codeObject instanceof RecordAutomationHandler recordAutomationHandler))
|
||||
{
|
||||
throw (new QException("The supplied code [" + codeClass.getName() + "] is not an instance of RecordAutomationHandler"));
|
||||
}
|
||||
return (recordAutomationHandler);
|
||||
}
|
||||
catch(QException qe)
|
||||
{
|
||||
throw (qe);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Error getting record automation handler for action [" + action.getName() + "]", e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QCustomPossibleValueProvider getCustomPossibleValueProvider(QPossibleValueSource possibleValueSource) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
Class<?> codeClass = Class.forName(possibleValueSource.getCustomCodeReference().getName());
|
||||
Object codeObject = codeClass.getConstructor().newInstance();
|
||||
if(!(codeObject instanceof QCustomPossibleValueProvider customPossibleValueProvider))
|
||||
{
|
||||
throw (new QException("The supplied code [" + codeClass.getName() + "] is not an instance of QCustomPossibleValueProvider"));
|
||||
}
|
||||
return (customPossibleValueProvider);
|
||||
}
|
||||
catch(QException qe)
|
||||
{
|
||||
throw (qe);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Error getting custom possible value provider for PVS [" + possibleValueSource.getName() + "]", e));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -290,7 +290,18 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "call one that doesn't take input param")
|
||||
public static String linkRecordEdit(AbstractActionInput input, String tableName, Serializable recordId) throws QException
|
||||
{
|
||||
return linkRecordEdit(tableName, recordId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String linkRecordEdit(String tableName, Serializable recordId) throws QException
|
||||
{
|
||||
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
||||
return (tablePath + "/" + recordId + "/edit");
|
||||
@ -317,7 +328,17 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "call one that doesn't take input param")
|
||||
public static String linkProcessForFilter(AbstractActionInput input, String processName, QQueryFilter filter) throws QException
|
||||
{
|
||||
return linkProcessForFilter(processName, filter);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String linkProcessForFilter(String processName, QQueryFilter filter) throws QException
|
||||
{
|
||||
QProcessMetaData process = QContext.getQInstance().getProcess(processName);
|
||||
if(process == null)
|
||||
@ -337,10 +358,21 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "call one that doesn't take input param")
|
||||
public static String linkProcessForRecord(AbstractActionInput input, String processName, Serializable recordId) throws QException
|
||||
{
|
||||
return linkProcessForRecord(processName, recordId);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String linkProcessForRecord(String processName, Serializable recordId) throws QException
|
||||
{
|
||||
QProcessMetaData process = QContext.getQInstance().getProcess(processName);
|
||||
String tableName = process.getTableName();
|
||||
|
@ -33,6 +33,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
/*******************************************************************************
|
||||
** a default implementation of MetaDataFilterInterface, that allows all the things
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "migrated to metaDataCustomizer")
|
||||
public class AllowAllMetaDataFilter implements MetaDataFilterInterface
|
||||
{
|
||||
|
||||
|
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.metadata;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** a default implementation of MetaDataFilterInterface, that is all noop.
|
||||
*******************************************************************************/
|
||||
public class DefaultNoopMetaDataActionCustomizer implements MetaDataActionCustomizerInterface
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public boolean allowTable(MetaDataInput input, QTableMetaData table)
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public boolean allowProcess(MetaDataInput input, QProcessMetaData process)
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public boolean allowReport(MetaDataInput input, QReportMetaData report)
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public boolean allowApp(MetaDataInput input, QAppMetaData app)
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public boolean allowWidget(MetaDataInput input, QWidgetMetaDataInterface widget)
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
|
||||
}
|
@ -23,10 +23,12 @@ package com.kingsrook.qqq.backend.core.actions.metadata;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionCheckResult;
|
||||
@ -34,6 +36,7 @@ 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.exceptions.QRuntimeException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
|
||||
@ -65,7 +68,7 @@ public class MetaDataAction
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(MetaDataAction.class);
|
||||
|
||||
private static Memoization<QInstance, MetaDataFilterInterface> metaDataFilterMemoization = new Memoization<>();
|
||||
private static Memoization<QInstance, MetaDataActionCustomizerInterface> metaDataActionCustomizerMemoization = new Memoization<>();
|
||||
|
||||
|
||||
|
||||
@ -79,7 +82,7 @@ public class MetaDataAction
|
||||
MetaDataOutput metaDataOutput = new MetaDataOutput();
|
||||
Map<String, AppTreeNode> treeNodes = new LinkedHashMap<>();
|
||||
|
||||
MetaDataFilterInterface filter = getMetaDataFilter();
|
||||
MetaDataActionCustomizerInterface customizer = getMetaDataActionCustomizer();
|
||||
|
||||
/////////////////////////////////////
|
||||
// map tables to frontend metadata //
|
||||
@ -90,7 +93,7 @@ public class MetaDataAction
|
||||
String tableName = entry.getKey();
|
||||
QTableMetaData table = entry.getValue();
|
||||
|
||||
if(!filter.allowTable(metaDataInput, table))
|
||||
if(!customizer.allowTable(metaDataInput, table))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -119,7 +122,7 @@ public class MetaDataAction
|
||||
String processName = entry.getKey();
|
||||
QProcessMetaData process = entry.getValue();
|
||||
|
||||
if(!filter.allowProcess(metaDataInput, process))
|
||||
if(!customizer.allowProcess(metaDataInput, process))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -144,7 +147,7 @@ public class MetaDataAction
|
||||
String reportName = entry.getKey();
|
||||
QReportMetaData report = entry.getValue();
|
||||
|
||||
if(!filter.allowReport(metaDataInput, report))
|
||||
if(!customizer.allowReport(metaDataInput, report))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -169,7 +172,7 @@ public class MetaDataAction
|
||||
String widgetName = entry.getKey();
|
||||
QWidgetMetaDataInterface widget = entry.getValue();
|
||||
|
||||
if(!filter.allowWidget(metaDataInput, widget))
|
||||
if(!customizer.allowWidget(metaDataInput, widget))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -206,7 +209,7 @@ public class MetaDataAction
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!filter.allowApp(metaDataInput, app))
|
||||
if(!customizer.allowApp(metaDataInput, app))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -292,11 +295,22 @@ public class MetaDataAction
|
||||
metaDataOutput.setBranding(QContext.getQInstance().getBranding());
|
||||
}
|
||||
|
||||
metaDataOutput.setEnvironmentValues(QContext.getQInstance().getEnvironmentValues());
|
||||
metaDataOutput.setEnvironmentValues(Objects.requireNonNullElse(QContext.getQInstance().getEnvironmentValues(), Collections.emptyMap()));
|
||||
|
||||
metaDataOutput.setHelpContents(QContext.getQInstance().getHelpContent());
|
||||
metaDataOutput.setHelpContents(Objects.requireNonNullElse(QContext.getQInstance().getHelpContent(), Collections.emptyMap()));
|
||||
|
||||
// todo post-customization - can do whatever w/ the result if you want?
|
||||
try
|
||||
{
|
||||
customizer.postProcess(metaDataOutput);
|
||||
}
|
||||
catch(QUserFacingException e)
|
||||
{
|
||||
LOG.debug("User-facing exception thrown in meta-data customizer post-processing", e);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Unexpected error thrown in meta-data customizer post-processing", e);
|
||||
}
|
||||
|
||||
return metaDataOutput;
|
||||
}
|
||||
@ -306,26 +320,36 @@ public class MetaDataAction
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private MetaDataFilterInterface getMetaDataFilter()
|
||||
private MetaDataActionCustomizerInterface getMetaDataActionCustomizer()
|
||||
{
|
||||
return metaDataFilterMemoization.getResult(QContext.getQInstance(), i ->
|
||||
return metaDataActionCustomizerMemoization.getResult(QContext.getQInstance(), i ->
|
||||
{
|
||||
MetaDataFilterInterface filter = null;
|
||||
QCodeReference metaDataFilterReference = QContext.getQInstance().getMetaDataFilter();
|
||||
if(metaDataFilterReference != null)
|
||||
MetaDataActionCustomizerInterface actionCustomizer = null;
|
||||
QCodeReference metaDataActionCustomizerReference = QContext.getQInstance().getMetaDataActionCustomizer();
|
||||
if(metaDataActionCustomizerReference != null)
|
||||
{
|
||||
filter = QCodeLoader.getAdHoc(MetaDataFilterInterface.class, metaDataFilterReference);
|
||||
LOG.debug("Using new meta-data filter of type: " + filter.getClass().getSimpleName());
|
||||
actionCustomizer = QCodeLoader.getAdHoc(MetaDataActionCustomizerInterface.class, metaDataActionCustomizerReference);
|
||||
LOG.debug("Using new meta-data actionCustomizer of type: " + actionCustomizer.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
if(filter == null)
|
||||
if(actionCustomizer == null)
|
||||
{
|
||||
filter = new AllowAllMetaDataFilter();
|
||||
LOG.debug("Using new default (allow-all) meta-data filter");
|
||||
QCodeReference metaDataFilterReference = QContext.getQInstance().getMetaDataFilter();
|
||||
if(metaDataFilterReference != null)
|
||||
{
|
||||
actionCustomizer = QCodeLoader.getAdHoc(MetaDataActionCustomizerInterface.class, metaDataFilterReference);
|
||||
LOG.debug("Using new meta-data actionCustomizer (via metaDataFilter reference) of type: " + actionCustomizer.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
return (filter);
|
||||
}).orElseThrow(() -> new QRuntimeException("Error getting metaDataFilter"));
|
||||
if(actionCustomizer == null)
|
||||
{
|
||||
actionCustomizer = new DefaultNoopMetaDataActionCustomizer();
|
||||
LOG.debug("Using new default (allow-all) meta-data actionCustomizer");
|
||||
}
|
||||
|
||||
return (actionCustomizer);
|
||||
}).orElseThrow(() -> new QRuntimeException("Error getting MetaDataActionCustomizer"));
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.metadata;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Interface for customizations that can be injected by an application into
|
||||
** the MetaDataAction - e.g., loading applicable meta-data for a user into a
|
||||
** frontend.
|
||||
*******************************************************************************/
|
||||
public interface MetaDataActionCustomizerInterface
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
boolean allowTable(MetaDataInput input, QTableMetaData table);
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
boolean allowProcess(MetaDataInput input, QProcessMetaData process);
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
boolean allowReport(MetaDataInput input, QReportMetaData report);
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
boolean allowApp(MetaDataInput input, QAppMetaData app);
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
boolean allowWidget(MetaDataInput input, QWidgetMetaDataInterface widget);
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
default void postProcess(MetaDataOutput metaDataOutput) throws QException
|
||||
{
|
||||
/////////////////////
|
||||
// noop by default //
|
||||
/////////////////////
|
||||
}
|
||||
|
||||
}
|
@ -22,43 +22,11 @@
|
||||
package com.kingsrook.qqq.backend.core.actions.metadata;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public interface MetaDataFilterInterface
|
||||
@Deprecated(since = "migrated to metaDataCustomizer")
|
||||
public interface MetaDataFilterInterface extends MetaDataActionCustomizerInterface
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
boolean allowTable(MetaDataInput input, QTableMetaData table);
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
boolean allowProcess(MetaDataInput input, QProcessMetaData process);
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
boolean allowReport(MetaDataInput input, QReportMetaData report);
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
boolean allowApp(MetaDataInput input, QAppMetaData app);
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
boolean allowWidget(MetaDataInput input, QWidgetMetaDataInterface widget);
|
||||
|
||||
}
|
||||
|
@ -54,6 +54,14 @@ public interface ExportStreamerInterface
|
||||
// noop in base class
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
default void setExportStyleCustomizer(ExportStyleCustomizerInterface exportStyleCustomizer)
|
||||
{
|
||||
// noop in base class
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Called once per sheet, before any rows are available. Meant to write a
|
||||
** header, for example.
|
||||
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. 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;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** interface for classes that can be used to customize visual style aspects of
|
||||
** exports/reports.
|
||||
**
|
||||
** Anticipates very different sub-interfaces based on the file type being generated,
|
||||
** and the capabilities of each. e.g., excel (bolds, fonts, cell merging) vs
|
||||
** json (different structure of objects).
|
||||
*******************************************************************************/
|
||||
public interface ExportStyleCustomizerInterface
|
||||
{
|
||||
}
|
@ -163,6 +163,17 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
reportStreamer = reportFormat.newReportStreamer();
|
||||
}
|
||||
|
||||
if(reportInput.getExportStyleCustomizer() != null)
|
||||
{
|
||||
ExportStyleCustomizerInterface styleCustomizer = QCodeLoader.getAdHoc(ExportStyleCustomizerInterface.class, reportInput.getExportStyleCustomizer());
|
||||
reportStreamer.setExportStyleCustomizer(styleCustomizer);
|
||||
}
|
||||
else if(report.getExportStyleCustomizer() != null)
|
||||
{
|
||||
ExportStyleCustomizerInterface styleCustomizer = QCodeLoader.getAdHoc(ExportStyleCustomizerInterface.class, report.getExportStyleCustomizer());
|
||||
reportStreamer.setExportStyleCustomizer(styleCustomizer);
|
||||
}
|
||||
|
||||
reportStreamer.preRun(reportInput.getReportDestination(), views);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -211,7 +222,8 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(dataSourceTableView.getViewCustomizer() != null)
|
||||
{
|
||||
Function<QReportView, QReportView> viewCustomizerFunction = QCodeLoader.getFunction(dataSourceTableView.getViewCustomizer());
|
||||
@SuppressWarnings("unchecked")
|
||||
Function<QReportView, QReportView> viewCustomizerFunction = QCodeLoader.getAdHoc(Function.class, dataSourceTableView.getViewCustomizer());
|
||||
if(viewCustomizerFunction instanceof ReportViewCustomizer reportViewCustomizer)
|
||||
{
|
||||
reportViewCustomizer.setReportInput(reportInput);
|
||||
@ -660,7 +672,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if any fields are 'showPossibleValueLabel', then move display values for them into the record's values map //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
for(QReportField column : tableView.getColumns())
|
||||
for(QReportField column : CollectionUtils.nonNullList(tableView.getColumns()))
|
||||
{
|
||||
if(column.getShowPossibleValueLabel())
|
||||
{
|
||||
|
@ -46,6 +46,7 @@ import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.ExportStreamerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.ExportStyleCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.ReportUtils;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
|
||||
@ -77,6 +78,7 @@ import org.apache.poi.xssf.usermodel.XSSFPivotTable;
|
||||
import org.apache.poi.xssf.usermodel.XSSFRow;
|
||||
import org.apache.poi.xssf.usermodel.XSSFSheet;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -112,9 +114,10 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
||||
public static final String EXCEL_DATE_FORMAT = "yyyy-MM-dd";
|
||||
public static final String EXCEL_DATE_TIME_FORMAT = "yyyy-MM-dd H:mm:ss";
|
||||
|
||||
private PoiExcelStylerInterface poiExcelStylerInterface = getStylerInterface();
|
||||
private ExcelPoiBasedStreamingStyleCustomizerInterface styleCustomizerInterface;
|
||||
|
||||
private Map<String, String> excelCellFormats;
|
||||
private Map<String, XSSFCellStyle> styles = new HashMap<>();
|
||||
private Map<String, XSSFCellStyle> styles = new HashMap<>();
|
||||
|
||||
private int rowNo = 0;
|
||||
private int sheetIndex = 1;
|
||||
@ -402,6 +405,7 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
||||
dateTimeStyle.setDataFormat(createHelper.createDataFormat().getFormat(EXCEL_DATE_TIME_FORMAT));
|
||||
styles.put("datetime", dateTimeStyle);
|
||||
|
||||
PoiExcelStylerInterface poiExcelStylerInterface = getStylerInterface();
|
||||
styles.put("title", poiExcelStylerInterface.createStyleForTitle(workbook, createHelper));
|
||||
styles.put("header", poiExcelStylerInterface.createStyleForHeader(workbook, createHelper));
|
||||
styles.put("footer", poiExcelStylerInterface.createStyleForFooter(workbook, createHelper));
|
||||
@ -413,6 +417,11 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
||||
XSSFCellStyle footerDateTimeStyle = poiExcelStylerInterface.createStyleForFooter(workbook, createHelper);
|
||||
footerDateTimeStyle.setDataFormat(createHelper.createDataFormat().getFormat(EXCEL_DATE_TIME_FORMAT));
|
||||
styles.put("footer-datetime", footerDateTimeStyle);
|
||||
|
||||
if(styleCustomizerInterface != null)
|
||||
{
|
||||
styleCustomizerInterface.customizeStyles(styles, workbook, createHelper);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -458,7 +467,7 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
||||
}
|
||||
else
|
||||
{
|
||||
sheetWriter.beginSheet();
|
||||
sheetWriter.beginSheet(view, styleCustomizerInterface);
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// put the title and header rows in the sheet //
|
||||
@ -560,6 +569,16 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static void setStyleForField(QRecord record, String fieldName, String styleName)
|
||||
{
|
||||
record.setDisplayValue(fieldName + ":excelStyle", styleName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -567,12 +586,12 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
||||
{
|
||||
sheetWriter.insertRow(rowNo++);
|
||||
|
||||
int styleIndex = -1;
|
||||
int baseStyleIndex = -1;
|
||||
int dateStyleIndex = styles.get("date").getIndex();
|
||||
int dateTimeStyleIndex = styles.get("datetime").getIndex();
|
||||
if(isFooter)
|
||||
{
|
||||
styleIndex = styles.get("footer").getIndex();
|
||||
baseStyleIndex = styles.get("footer").getIndex();
|
||||
dateStyleIndex = styles.get("footer-date").getIndex();
|
||||
dateTimeStyleIndex = styles.get("footer-datetime").getIndex();
|
||||
}
|
||||
@ -582,6 +601,13 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
||||
{
|
||||
Serializable value = qRecord.getValue(field.getName());
|
||||
|
||||
String overrideStyleName = qRecord.getDisplayValue(field.getName() + ":excelStyle");
|
||||
int styleIndex = baseStyleIndex;
|
||||
if(overrideStyleName != null)
|
||||
{
|
||||
styleIndex = styles.get(overrideStyleName).getIndex();
|
||||
}
|
||||
|
||||
if(value != null)
|
||||
{
|
||||
if(value instanceof String s)
|
||||
@ -706,7 +732,7 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
||||
{
|
||||
if(!ReportType.PIVOT.equals(currentView.getType()))
|
||||
{
|
||||
sheetWriter.endSheet();
|
||||
sheetWriter.endSheet(currentView, styleCustomizerInterface);
|
||||
}
|
||||
|
||||
activeSheetWriter.flush();
|
||||
@ -815,7 +841,29 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
||||
*******************************************************************************/
|
||||
protected PoiExcelStylerInterface getStylerInterface()
|
||||
{
|
||||
if(styleCustomizerInterface != null)
|
||||
{
|
||||
return styleCustomizerInterface.getExcelStyler();
|
||||
}
|
||||
|
||||
return (new PlainPoiExcelStyler());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void setExportStyleCustomizer(ExportStyleCustomizerInterface exportStyleCustomizer)
|
||||
{
|
||||
if(exportStyleCustomizer instanceof ExcelPoiBasedStreamingStyleCustomizerInterface poiExcelStylerInterface)
|
||||
{
|
||||
this.styleCustomizerInterface = poiExcelStylerInterface;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.debug("Supplied export style customizer is not an instance of ExcelPoiStyleCustomizerInterface, so will not be used for an excel export", logPair("exportStyleCustomizerClass", exportStyleCustomizer.getClass().getSimpleName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. 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.excel.poi;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.ExportStyleCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
|
||||
import org.apache.poi.ss.usermodel.CreationHelper;
|
||||
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** style customization points for Excel files generated via our streaming POI.
|
||||
*******************************************************************************/
|
||||
public interface ExcelPoiBasedStreamingStyleCustomizerInterface extends ExportStyleCustomizerInterface
|
||||
{
|
||||
/***************************************************************************
|
||||
** slightly legacy way we did excel styles - but get an instance of object
|
||||
** that defaults "default" styles (header, footer, etc).
|
||||
***************************************************************************/
|
||||
default PoiExcelStylerInterface getExcelStyler()
|
||||
{
|
||||
return (new PlainPoiExcelStyler());
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** either change "default" styles put in the styles map, or create new ones
|
||||
** which can then be applied to row/field values (cells) via:
|
||||
** ExcelPoiBasedStreamingExportStreamer.setStyleForField(row, fieldName, styleName);
|
||||
***************************************************************************/
|
||||
default void customizeStyles(Map<String, XSSFCellStyle> styles, XSSFWorkbook workbook, CreationHelper createHelper)
|
||||
{
|
||||
//////////////////
|
||||
// noop default //
|
||||
//////////////////
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** for a given view (sheet), return a list of custom column widths.
|
||||
** any nulls in the list are ignored (so default width is used).
|
||||
***************************************************************************/
|
||||
default List<Integer> getColumnWidthsForView(QReportView view)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** for a given view (sheet), return a list of any ranges which should be
|
||||
** merged, as in "A1:C1" (first three cells in first row).
|
||||
***************************************************************************/
|
||||
default List<String> getMergedRangesForView(QReportView view)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
}
|
@ -25,7 +25,10 @@ package com.kingsrook.qqq.backend.core.actions.reporting.excel.poi;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import org.apache.poi.ss.util.CellReference;
|
||||
|
||||
|
||||
@ -53,13 +56,33 @@ public class StreamedSheetWriter
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void beginSheet() throws IOException
|
||||
public void beginSheet(QReportView view, ExcelPoiBasedStreamingStyleCustomizerInterface styleCustomizerInterface) throws IOException
|
||||
{
|
||||
writer.write("""
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
|
||||
<sheetData>""");
|
||||
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">""");
|
||||
|
||||
if(styleCustomizerInterface != null && view != null)
|
||||
{
|
||||
List<Integer> columnWidths = styleCustomizerInterface.getColumnWidthsForView(view);
|
||||
if(CollectionUtils.nullSafeHasContents(columnWidths))
|
||||
{
|
||||
writer.write("<cols>");
|
||||
for(int i = 0; i < columnWidths.size(); i++)
|
||||
{
|
||||
Integer width = columnWidths.get(i);
|
||||
if(width != null)
|
||||
{
|
||||
writer.write("""
|
||||
<col min="%d" max="%d" width="%d" customWidth="1"/>
|
||||
""".formatted(i + 1, i + 1, width));
|
||||
}
|
||||
}
|
||||
writer.write("</cols>");
|
||||
}
|
||||
}
|
||||
|
||||
writer.write("<sheetData>");
|
||||
}
|
||||
|
||||
|
||||
@ -67,11 +90,25 @@ public class StreamedSheetWriter
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void endSheet() throws IOException
|
||||
public void endSheet(QReportView view, ExcelPoiBasedStreamingStyleCustomizerInterface styleCustomizerInterface) throws IOException
|
||||
{
|
||||
writer.write("""
|
||||
</sheetData>
|
||||
</worksheet>""");
|
||||
writer.write("</sheetData>");
|
||||
|
||||
if(styleCustomizerInterface != null && view != null)
|
||||
{
|
||||
List<String> mergedRanges = styleCustomizerInterface.getMergedRangesForView(view);
|
||||
if(CollectionUtils.nullSafeHasContents(mergedRanges))
|
||||
{
|
||||
writer.write(String.format("<mergeCells count=\"%d\">", mergedRanges.size()));
|
||||
for(String range : mergedRanges)
|
||||
{
|
||||
writer.write(String.format("<mergeCell ref=\"%s\"/>", range));
|
||||
}
|
||||
writer.write("</mergeCells>");
|
||||
}
|
||||
}
|
||||
|
||||
writer.write("</worksheet>");
|
||||
}
|
||||
|
||||
|
||||
@ -151,7 +188,7 @@ public class StreamedSheetWriter
|
||||
{
|
||||
rs.append(""");
|
||||
}
|
||||
else if (c < 32 && c != '\t' && c != '\n')
|
||||
else if(c < 32 && c != '\t' && c != '\n')
|
||||
{
|
||||
rs.append(' ');
|
||||
}
|
||||
|
@ -53,7 +53,8 @@ public class QJavaExecutor implements QCodeExecutor
|
||||
Serializable output;
|
||||
try
|
||||
{
|
||||
Function<Map<String, Object>, Serializable> function = QCodeLoader.getFunction(codeReference);
|
||||
@SuppressWarnings("unchecked")
|
||||
Function<Map<String, Object>, Serializable> function = QCodeLoader.getAdHoc(Function.class, codeReference);
|
||||
output = function.apply(context);
|
||||
}
|
||||
catch(Exception e)
|
||||
|
@ -341,7 +341,7 @@ public class QPossibleValueTranslator
|
||||
|
||||
try
|
||||
{
|
||||
QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource);
|
||||
QCustomPossibleValueProvider<?> customPossibleValueProvider = QCodeLoader.getAdHoc(QCustomPossibleValueProvider.class, possibleValueSource.getCustomCodeReference());
|
||||
return (formatPossibleValue(possibleValueSource, customPossibleValueProvider.getPossibleValue(value)));
|
||||
}
|
||||
catch(Exception e)
|
||||
|
@ -424,7 +424,7 @@ public class SearchPossibleValueSourceAction
|
||||
{
|
||||
try
|
||||
{
|
||||
QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource);
|
||||
QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getAdHoc(QCustomPossibleValueProvider.class, possibleValueSource.getCustomCodeReference());
|
||||
List<QPossibleValue<?>> possibleValues = customPossibleValueProvider.search(input);
|
||||
|
||||
SearchPossibleValueSourceOutput output = new SearchPossibleValueSourceOutput();
|
||||
|
@ -1425,7 +1425,7 @@ public class QInstanceEnricher
|
||||
{
|
||||
try
|
||||
{
|
||||
QCustomPossibleValueProvider<?> customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource);
|
||||
QCustomPossibleValueProvider<?> customPossibleValueProvider = QCodeLoader.getAdHoc(QCustomPossibleValueProvider.class, possibleValueSource.getCustomCodeReference());
|
||||
|
||||
Method getPossibleValueMethod = customPossibleValueProvider.getClass().getDeclaredMethod("getPossibleValue", Serializable.class);
|
||||
Type returnType = getPossibleValueMethod.getGenericReturnType();
|
||||
|
@ -45,6 +45,7 @@ import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.JoinGraph;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataActionCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataFilterInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.customizers.ReportCustomRecordSourceInterface;
|
||||
@ -245,6 +246,11 @@ public class QInstanceValidator
|
||||
{
|
||||
validateSimpleCodeReference("Instance metaDataFilter ", qInstance.getMetaDataFilter(), MetaDataFilterInterface.class);
|
||||
}
|
||||
|
||||
if(qInstance.getMetaDataActionCustomizer() != null)
|
||||
{
|
||||
validateSimpleCodeReference("Instance metaDataActionCustomizer ", qInstance.getMetaDataActionCustomizer(), MetaDataActionCustomizerInterface.class);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -28,6 +28,7 @@ import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.ExportStreamerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||
|
||||
|
||||
@ -44,6 +45,7 @@ public class ReportInput extends AbstractTableActionInput
|
||||
private ReportDestination reportDestination;
|
||||
|
||||
private Supplier<? extends ExportStreamerInterface> overrideExportStreamerSupplier;
|
||||
private QCodeReference exportStyleCustomizer;
|
||||
|
||||
|
||||
|
||||
@ -208,4 +210,35 @@ public class ReportInput extends AbstractTableActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for exportStyleCustomizer
|
||||
*******************************************************************************/
|
||||
public QCodeReference getExportStyleCustomizer()
|
||||
{
|
||||
return (this.exportStyleCustomizer);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for exportStyleCustomizer
|
||||
*******************************************************************************/
|
||||
public void setExportStyleCustomizer(QCodeReference exportStyleCustomizer)
|
||||
{
|
||||
this.exportStyleCustomizer = exportStyleCustomizer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for exportStyleCustomizer
|
||||
*******************************************************************************/
|
||||
public ReportInput withExportStyleCustomizer(QCodeReference exportStyleCustomizer)
|
||||
{
|
||||
this.exportStyleCustomizer = exportStyleCustomizer;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -583,4 +583,31 @@ public abstract class QRecordEntity
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static String getTableName(Class<? extends QRecordEntity> entityClass) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
Field tableNameField = entityClass.getDeclaredField("TABLE_NAME");
|
||||
String tableNameValue = (String) tableNameField.get(null);
|
||||
return (tableNameValue);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Could not get TABLE_NAME from entity class: " + entityClass.getSimpleName(), e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** named without the 'get' to avoid conflict w/ entity fields named that...
|
||||
***************************************************************************/
|
||||
public String tableName() throws QException
|
||||
{
|
||||
return (getTableName(this.getClass()));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -417,7 +417,7 @@ public class MetaDataProducerHelper
|
||||
return (null);
|
||||
}
|
||||
|
||||
ChildJoinFromRecordEntityGenericMetaDataProducer producer = new ChildJoinFromRecordEntityGenericMetaDataProducer(childTableName, parentTableName, possibleValueFieldName);
|
||||
ChildJoinFromRecordEntityGenericMetaDataProducer producer = new ChildJoinFromRecordEntityGenericMetaDataProducer(childTableName, parentTableName, possibleValueFieldName, childTable.childJoin().orderBy());
|
||||
producer.setSourceClass(entityClass);
|
||||
return producer;
|
||||
}
|
||||
|
@ -116,8 +116,11 @@ public class QInstance
|
||||
private QPermissionRules defaultPermissionRules = QPermissionRules.defaultInstance();
|
||||
private QAuditRules defaultAuditRules = QAuditRules.defaultInstanceLevelNone();
|
||||
|
||||
@Deprecated(since = "migrated to metaDataCustomizer")
|
||||
private QCodeReference metaDataFilter = null;
|
||||
|
||||
private QCodeReference metaDataActionCustomizer = null;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// todo - lock down the object (no more changes allowed) after it's been validated? //
|
||||
// if doing so, may need to copy all of the collections into read-only versions... //
|
||||
@ -1495,6 +1498,7 @@ public class QInstance
|
||||
/*******************************************************************************
|
||||
** Getter for metaDataFilter
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "migrated to metaDataCustomizer")
|
||||
public QCodeReference getMetaDataFilter()
|
||||
{
|
||||
return (this.metaDataFilter);
|
||||
@ -1505,6 +1509,7 @@ public class QInstance
|
||||
/*******************************************************************************
|
||||
** Setter for metaDataFilter
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "migrated to metaDataCustomizer")
|
||||
public void setMetaDataFilter(QCodeReference metaDataFilter)
|
||||
{
|
||||
this.metaDataFilter = metaDataFilter;
|
||||
@ -1515,6 +1520,7 @@ public class QInstance
|
||||
/*******************************************************************************
|
||||
** Fluent setter for metaDataFilter
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "migrated to metaDataCustomizer")
|
||||
public QInstance withMetaDataFilter(QCodeReference metaDataFilter)
|
||||
{
|
||||
this.metaDataFilter = metaDataFilter;
|
||||
@ -1586,4 +1592,35 @@ public class QInstance
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for metaDataActionCustomizer
|
||||
*******************************************************************************/
|
||||
public QCodeReference getMetaDataActionCustomizer()
|
||||
{
|
||||
return (this.metaDataActionCustomizer);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for metaDataActionCustomizer
|
||||
*******************************************************************************/
|
||||
public void setMetaDataActionCustomizer(QCodeReference metaDataActionCustomizer)
|
||||
{
|
||||
this.metaDataActionCustomizer = metaDataActionCustomizer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for metaDataActionCustomizer
|
||||
*******************************************************************************/
|
||||
public QInstance withMetaDataActionCustomizer(QCodeReference metaDataActionCustomizer)
|
||||
{
|
||||
this.metaDataActionCustomizer = metaDataActionCustomizer;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,269 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.branding;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** element of BrandingMetaData - content to send to a frontend for showing a
|
||||
** user across the whole UI - e.g., what environment you're in, or a message
|
||||
** about your account - site announcements, etc.
|
||||
*******************************************************************************/
|
||||
public class Banner implements Serializable, Cloneable
|
||||
{
|
||||
private Severity severity;
|
||||
private String textColor;
|
||||
private String backgroundColor;
|
||||
private String messageText;
|
||||
private String messageHTML;
|
||||
|
||||
private Map<String, Serializable> additionalStyles;
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public enum Severity
|
||||
{
|
||||
INFO, WARNING, ERROR, SUCCESS
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public Banner clone()
|
||||
{
|
||||
try
|
||||
{
|
||||
Banner clone = (Banner) super.clone();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// copy mutable state here, so the clone can't change the internals of the original //
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
if(additionalStyles != null)
|
||||
{
|
||||
clone.setAdditionalStyles(new LinkedHashMap<>(additionalStyles));
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
catch(CloneNotSupportedException e)
|
||||
{
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for textColor
|
||||
*******************************************************************************/
|
||||
public String getTextColor()
|
||||
{
|
||||
return (this.textColor);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for textColor
|
||||
*******************************************************************************/
|
||||
public void setTextColor(String textColor)
|
||||
{
|
||||
this.textColor = textColor;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for textColor
|
||||
*******************************************************************************/
|
||||
public Banner withTextColor(String textColor)
|
||||
{
|
||||
this.textColor = textColor;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for backgroundColor
|
||||
*******************************************************************************/
|
||||
public String getBackgroundColor()
|
||||
{
|
||||
return (this.backgroundColor);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for backgroundColor
|
||||
*******************************************************************************/
|
||||
public void setBackgroundColor(String backgroundColor)
|
||||
{
|
||||
this.backgroundColor = backgroundColor;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for backgroundColor
|
||||
*******************************************************************************/
|
||||
public Banner withBackgroundColor(String backgroundColor)
|
||||
{
|
||||
this.backgroundColor = backgroundColor;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for additionalStyles
|
||||
*******************************************************************************/
|
||||
public Map<String, Serializable> getAdditionalStyles()
|
||||
{
|
||||
return (this.additionalStyles);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for additionalStyles
|
||||
*******************************************************************************/
|
||||
public void setAdditionalStyles(Map<String, Serializable> additionalStyles)
|
||||
{
|
||||
this.additionalStyles = additionalStyles;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for additionalStyles
|
||||
*******************************************************************************/
|
||||
public Banner withAdditionalStyles(Map<String, Serializable> additionalStyles)
|
||||
{
|
||||
this.additionalStyles = additionalStyles;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for messageText
|
||||
*******************************************************************************/
|
||||
public String getMessageText()
|
||||
{
|
||||
return (this.messageText);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for messageText
|
||||
*******************************************************************************/
|
||||
public void setMessageText(String messageText)
|
||||
{
|
||||
this.messageText = messageText;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for messageText
|
||||
*******************************************************************************/
|
||||
public Banner withMessageText(String messageText)
|
||||
{
|
||||
this.messageText = messageText;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for messageHTML
|
||||
*******************************************************************************/
|
||||
public String getMessageHTML()
|
||||
{
|
||||
return (this.messageHTML);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for messageHTML
|
||||
*******************************************************************************/
|
||||
public void setMessageHTML(String messageHTML)
|
||||
{
|
||||
this.messageHTML = messageHTML;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for messageHTML
|
||||
*******************************************************************************/
|
||||
public Banner withMessageHTML(String messageHTML)
|
||||
{
|
||||
this.messageHTML = messageHTML;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for severity
|
||||
*******************************************************************************/
|
||||
public Severity getSeverity()
|
||||
{
|
||||
return (this.severity);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for severity
|
||||
*******************************************************************************/
|
||||
public void setSeverity(Severity severity)
|
||||
{
|
||||
this.severity = severity;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for severity
|
||||
*******************************************************************************/
|
||||
public Banner withSeverity(Severity severity)
|
||||
{
|
||||
this.severity = severity;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.branding;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** interface to define keys for where banners should be displayed.
|
||||
** expect frontends to implement this interface with enums of known possible values
|
||||
*******************************************************************************/
|
||||
public interface BannerSlot
|
||||
{
|
||||
}
|
@ -22,6 +22,9 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.branding;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||
|
||||
@ -30,7 +33,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||
** Meta-Data to define branding in a QQQ instance.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QBrandingMetaData implements TopLevelMetaDataInterface
|
||||
public class QBrandingMetaData implements TopLevelMetaDataInterface, Cloneable, Serializable
|
||||
{
|
||||
private String companyName;
|
||||
private String companyUrl;
|
||||
@ -39,9 +42,45 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
|
||||
private String icon;
|
||||
private String accentColor;
|
||||
|
||||
@Deprecated(since = "migrate to use banners map instead")
|
||||
private String environmentBannerText;
|
||||
|
||||
@Deprecated(since = "migrate to use banners map instead")
|
||||
private String environmentBannerColor;
|
||||
|
||||
private Map<BannerSlot, Banner> banners;
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QBrandingMetaData clone()
|
||||
{
|
||||
try
|
||||
{
|
||||
QBrandingMetaData clone = (QBrandingMetaData) super.clone();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// copy mutable state here, so the clone can't change the internals of the original //
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
if(banners != null)
|
||||
{
|
||||
clone.banners = new LinkedHashMap<>();
|
||||
for(Map.Entry<BannerSlot, Banner> entry : this.banners.entrySet())
|
||||
{
|
||||
clone.banners.put(entry.getKey(), entry.getValue().clone());
|
||||
}
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
catch(CloneNotSupportedException e)
|
||||
{
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -267,6 +306,7 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
|
||||
/*******************************************************************************
|
||||
** Getter for environmentBannerText
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "migrate to use banners map instead")
|
||||
public String getEnvironmentBannerText()
|
||||
{
|
||||
return (this.environmentBannerText);
|
||||
@ -277,6 +317,7 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
|
||||
/*******************************************************************************
|
||||
** Setter for environmentBannerText
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "migrate to use banners map instead")
|
||||
public void setEnvironmentBannerText(String environmentBannerText)
|
||||
{
|
||||
this.environmentBannerText = environmentBannerText;
|
||||
@ -287,6 +328,7 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
|
||||
/*******************************************************************************
|
||||
** Fluent setter for environmentBannerText
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "migrate to use banners map instead")
|
||||
public QBrandingMetaData withEnvironmentBannerText(String environmentBannerText)
|
||||
{
|
||||
this.environmentBannerText = environmentBannerText;
|
||||
@ -298,6 +340,7 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
|
||||
/*******************************************************************************
|
||||
** Getter for environmentBannerColor
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "migrate to use banners map instead")
|
||||
public String getEnvironmentBannerColor()
|
||||
{
|
||||
return (this.environmentBannerColor);
|
||||
@ -308,6 +351,7 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
|
||||
/*******************************************************************************
|
||||
** Setter for environmentBannerColor
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "migrate to use banners map instead")
|
||||
public void setEnvironmentBannerColor(String environmentBannerColor)
|
||||
{
|
||||
this.environmentBannerColor = environmentBannerColor;
|
||||
@ -318,6 +362,7 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
|
||||
/*******************************************************************************
|
||||
** Fluent setter for environmentBannerColor
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "migrate to use banners map instead")
|
||||
public QBrandingMetaData withEnvironmentBannerColor(String environmentBannerColor)
|
||||
{
|
||||
this.environmentBannerColor = environmentBannerColor;
|
||||
@ -334,4 +379,52 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
|
||||
{
|
||||
qInstance.setBranding(this);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for banners
|
||||
*******************************************************************************/
|
||||
public Map<BannerSlot, Banner> getBanners()
|
||||
{
|
||||
return (this.banners);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for banners
|
||||
*******************************************************************************/
|
||||
public void setBanners(Map<BannerSlot, Banner> banners)
|
||||
{
|
||||
this.banners = banners;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for banners
|
||||
*******************************************************************************/
|
||||
public QBrandingMetaData withBanners(Map<BannerSlot, Banner> banners)
|
||||
{
|
||||
this.banners = banners;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public QBrandingMetaData withBanner(BannerSlot slot, Banner banner)
|
||||
{
|
||||
if(this.banners == null)
|
||||
{
|
||||
this.banners = new LinkedHashMap<>();
|
||||
}
|
||||
this.banners.put(slot, banner);
|
||||
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.code;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** an object which is intended to be constructed via a CodeReference, and,
|
||||
** moreso, after it is created, then the initialize method here gets called,
|
||||
** passing the codeRefernce in - e.g., to do additional initalization of the
|
||||
** object, e.g., properties in a QCodeReferenceWithProperties
|
||||
*******************************************************************************/
|
||||
public interface InitializableViaCodeReference
|
||||
{
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
void initialize(QCodeReference codeReference);
|
||||
|
||||
}
|
@ -29,7 +29,7 @@ import java.io.Serializable;
|
||||
** Pointer to code to be ran by the qqq framework, e.g., for custom behavior -
|
||||
** maybe process steps, maybe customization to a table, etc.
|
||||
*******************************************************************************/
|
||||
public class QCodeReference implements Serializable
|
||||
public class QCodeReference implements Serializable, Cloneable
|
||||
{
|
||||
private String name;
|
||||
private QCodeType codeType;
|
||||
@ -58,6 +58,25 @@ public class QCodeReference implements Serializable
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QCodeReference clone()
|
||||
{
|
||||
try
|
||||
{
|
||||
QCodeReference clone = (QCodeReference) super.clone();
|
||||
return clone;
|
||||
}
|
||||
catch(CloneNotSupportedException e)
|
||||
{
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -179,5 +198,4 @@ public class QCodeReference implements Serializable
|
||||
this.inlineCode = inlineCode;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.code;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** a code reference that also has a map of properties. This object (with the
|
||||
** properties) will be passed in to the referenced object, if it implements
|
||||
** InitializableViaCodeReference.
|
||||
*******************************************************************************/
|
||||
public class QCodeReferenceWithProperties extends QCodeReference
|
||||
{
|
||||
private final Map<String, Serializable> properties;
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public QCodeReferenceWithProperties(Class<?> javaClass, Map<String, Serializable> properties)
|
||||
{
|
||||
super(javaClass);
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for properties
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, Serializable> getProperties()
|
||||
{
|
||||
return properties;
|
||||
}
|
||||
}
|
@ -62,7 +62,8 @@ public class WidgetAdHocValue extends AbstractWidgetValueSource
|
||||
context.putAll(inputValues);
|
||||
}
|
||||
|
||||
Function<Object, Object> function = QCodeLoader.getFunction(codeReference);
|
||||
@SuppressWarnings("unchecked")
|
||||
Function<Object, Object> function = QCodeLoader.getAdHoc(Function.class, codeReference);
|
||||
Object result = function.apply(context);
|
||||
return (result);
|
||||
}
|
||||
|
@ -55,6 +55,7 @@ public class QFrontendFieldMetaData implements Serializable
|
||||
private String possibleValueSourceName;
|
||||
private String displayFormat;
|
||||
private Serializable defaultValue;
|
||||
private Integer maxLength;
|
||||
|
||||
private List<FieldAdornment> adornments;
|
||||
private List<QHelpContent> helpContents;
|
||||
@ -85,6 +86,7 @@ public class QFrontendFieldMetaData implements Serializable
|
||||
this.defaultValue = fieldMetaData.getDefaultValue();
|
||||
this.helpContents = fieldMetaData.getHelpContents();
|
||||
this.inlinePossibleValueSource = fieldMetaData.getInlinePossibleValueSource();
|
||||
this.maxLength = fieldMetaData.getMaxLength();
|
||||
|
||||
for(FieldBehavior<?> behavior : CollectionUtils.nonNullCollection(fieldMetaData.getBehaviors()))
|
||||
{
|
||||
|
@ -48,6 +48,8 @@ public class QFrontendProcessMetaData
|
||||
private String label;
|
||||
private String tableName;
|
||||
private boolean isHidden;
|
||||
private Integer minInputRecords;
|
||||
private Integer maxInputRecords;
|
||||
|
||||
private QIcon icon;
|
||||
|
||||
@ -72,6 +74,8 @@ public class QFrontendProcessMetaData
|
||||
this.tableName = processMetaData.getTableName();
|
||||
this.isHidden = processMetaData.getIsHidden();
|
||||
this.stepFlow = processMetaData.getStepFlow().toString();
|
||||
this.minInputRecords = processMetaData.getMinInputRecords();
|
||||
this.maxInputRecords = processMetaData.getMaxInputRecords();
|
||||
|
||||
if(includeSteps)
|
||||
{
|
||||
@ -213,4 +217,27 @@ public class QFrontendProcessMetaData
|
||||
{
|
||||
return icon;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for minInputRecords
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Integer getMinInputRecords()
|
||||
{
|
||||
return minInputRecords;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for maxInputRecords
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Integer getMaxInputRecords()
|
||||
{
|
||||
return maxInputRecords;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.layout;
|
||||
** Future may allow something like a "namespace", and/or multiple icons for
|
||||
** use in different frontends, etc.
|
||||
*******************************************************************************/
|
||||
public class QIcon
|
||||
public class QIcon implements Cloneable
|
||||
{
|
||||
private String name;
|
||||
private String path;
|
||||
@ -58,6 +58,25 @@ public class QIcon
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QIcon clone()
|
||||
{
|
||||
try
|
||||
{
|
||||
QIcon clone = (QIcon) super.clone();
|
||||
return clone;
|
||||
}
|
||||
catch(CloneNotSupportedException e)
|
||||
{
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for name
|
||||
**
|
||||
@ -154,6 +173,4 @@ public class QIcon
|
||||
this.color = color;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -24,11 +24,13 @@ package com.kingsrook.qqq.backend.core.model.metadata.producers;
|
||||
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.ChildJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
@ -39,12 +41,11 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
**
|
||||
** e.g., Orders & LineItems - on the Order entity
|
||||
** <code>
|
||||
@QMetaDataProducingEntity(
|
||||
childTables = { @ChildTable(
|
||||
childTableEntityClass = LineItem.class,
|
||||
childJoin = @ChildJoin(enabled = true),
|
||||
childRecordListWidget = @ChildRecordListWidget(enabled = true, label = "Order Lines"))
|
||||
}
|
||||
@QMetaDataProducingEntity( childTables = { @ChildTable(
|
||||
childTableEntityClass = LineItem.class,
|
||||
childJoin = @ChildJoin(enabled = true),
|
||||
childRecordListWidget = @ChildRecordListWidget(enabled = true, label = "Order Lines"))
|
||||
}
|
||||
)
|
||||
public class Order extends QRecordEntity
|
||||
** </code>
|
||||
@ -62,13 +63,16 @@ public class ChildJoinFromRecordEntityGenericMetaDataProducer implements MetaDat
|
||||
private String parentTableName; // e.g., order
|
||||
private String foreignKeyFieldName; // e.g., orderId
|
||||
|
||||
private ChildJoin.OrderBy[] orderBys;
|
||||
|
||||
private Class<?> sourceClass;
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public ChildJoinFromRecordEntityGenericMetaDataProducer(String childTableName, String parentTableName, String foreignKeyFieldName)
|
||||
public ChildJoinFromRecordEntityGenericMetaDataProducer(String childTableName, String parentTableName, String foreignKeyFieldName, ChildJoin.OrderBy[] orderBys)
|
||||
{
|
||||
Objects.requireNonNull(childTableName, "childTableName cannot be null");
|
||||
Objects.requireNonNull(parentTableName, "parentTableName cannot be null");
|
||||
@ -77,6 +81,7 @@ public class ChildJoinFromRecordEntityGenericMetaDataProducer implements MetaDat
|
||||
this.childTableName = childTableName;
|
||||
this.parentTableName = parentTableName;
|
||||
this.foreignKeyFieldName = foreignKeyFieldName;
|
||||
this.orderBys = orderBys;
|
||||
}
|
||||
|
||||
|
||||
@ -87,18 +92,39 @@ public class ChildJoinFromRecordEntityGenericMetaDataProducer implements MetaDat
|
||||
@Override
|
||||
public QJoinMetaData produce(QInstance qInstance) throws QException
|
||||
{
|
||||
QTableMetaData possibleValueTable = qInstance.getTable(parentTableName);
|
||||
if(possibleValueTable == null)
|
||||
QTableMetaData parentTable = qInstance.getTable(parentTableName);
|
||||
if(parentTable == null)
|
||||
{
|
||||
throw (new QException("Could not find tableMetaData " + parentTableName));
|
||||
}
|
||||
|
||||
QTableMetaData childTable = qInstance.getTable(childTableName);
|
||||
if(childTable == null)
|
||||
{
|
||||
throw (new QException("Could not find tableMetaData " + childTable));
|
||||
}
|
||||
|
||||
QJoinMetaData join = new QJoinMetaData()
|
||||
.withLeftTable(parentTableName)
|
||||
.withRightTable(childTableName)
|
||||
.withInferredName()
|
||||
.withType(JoinType.ONE_TO_MANY)
|
||||
.withJoinOn(new JoinOn(possibleValueTable.getPrimaryKeyField(), foreignKeyFieldName));
|
||||
.withJoinOn(new JoinOn(parentTable.getPrimaryKeyField(), foreignKeyFieldName));
|
||||
|
||||
if(orderBys != null && orderBys.length > 0)
|
||||
{
|
||||
for(ChildJoin.OrderBy orderBy : orderBys)
|
||||
{
|
||||
join.withOrderBy(new QFilterOrderBy(orderBy.fieldName(), orderBy.isAscending()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//////////////////////////////////////////////////////////
|
||||
// by default, sort by the id of the child table... mmm //
|
||||
//////////////////////////////////////////////////////////
|
||||
join.withOrderBy(new QFilterOrderBy(childTable.getPrimaryKeyField()));
|
||||
}
|
||||
|
||||
return (join);
|
||||
}
|
||||
@ -126,6 +152,7 @@ public class ChildJoinFromRecordEntityGenericMetaDataProducer implements MetaDat
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for sourceClass
|
||||
**
|
||||
|
@ -35,4 +35,16 @@ import java.lang.annotation.RetentionPolicy;
|
||||
public @interface ChildJoin
|
||||
{
|
||||
boolean enabled();
|
||||
|
||||
OrderBy[] orderBy() default { };
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@interface OrderBy
|
||||
{
|
||||
String fieldName();
|
||||
|
||||
boolean isAscending() default true;
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
** (optionally along with queryJoins and queryInputCustomizer) is used.
|
||||
** - else a staticDataSupplier is used.
|
||||
*******************************************************************************/
|
||||
public class QReportDataSource
|
||||
public class QReportDataSource implements Cloneable
|
||||
{
|
||||
private String name;
|
||||
|
||||
@ -55,6 +55,39 @@ public class QReportDataSource
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QReportDataSource clone()
|
||||
{
|
||||
try
|
||||
{
|
||||
QReportDataSource clone = (QReportDataSource) super.clone();
|
||||
if(queryFilter != null)
|
||||
{
|
||||
clone.queryFilter = queryFilter.clone();
|
||||
}
|
||||
|
||||
if(queryJoins != null)
|
||||
{
|
||||
clone.queryJoins = new ArrayList<>();
|
||||
for(QueryJoin join : queryJoins)
|
||||
{
|
||||
queryJoins.add(join.clone());
|
||||
}
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
catch(CloneNotSupportedException e)
|
||||
{
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for name
|
||||
**
|
||||
@ -274,6 +307,7 @@ public class QReportDataSource
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for customRecordSource
|
||||
*******************************************************************************/
|
||||
@ -303,5 +337,4 @@ public class QReportDataSource
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
@ -37,7 +38,7 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
/*******************************************************************************
|
||||
** Meta-data definition of a report generated by QQQ
|
||||
*******************************************************************************/
|
||||
public class QReportMetaData implements QAppChildMetaData, MetaDataWithPermissionRules, TopLevelMetaDataInterface
|
||||
public class QReportMetaData implements QAppChildMetaData, MetaDataWithPermissionRules, TopLevelMetaDataInterface, Cloneable
|
||||
{
|
||||
private String name;
|
||||
private String label;
|
||||
@ -52,6 +53,72 @@ public class QReportMetaData implements QAppChildMetaData, MetaDataWithPermissio
|
||||
|
||||
private QIcon icon;
|
||||
|
||||
private QCodeReference exportStyleCustomizer;
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QReportMetaData clone()
|
||||
{
|
||||
try
|
||||
{
|
||||
QReportMetaData clone = (QReportMetaData) super.clone();
|
||||
|
||||
//////////////////////////////
|
||||
// Deep copy mutable fields //
|
||||
//////////////////////////////
|
||||
if(this.inputFields != null)
|
||||
{
|
||||
clone.inputFields = new ArrayList<>();
|
||||
for(QFieldMetaData inputField : this.inputFields)
|
||||
{
|
||||
clone.inputFields.add(inputField.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if(this.dataSources != null)
|
||||
{
|
||||
clone.dataSources = new ArrayList<>();
|
||||
for(QReportDataSource dataSource : this.dataSources)
|
||||
{
|
||||
clone.dataSources.add(dataSource.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if(this.views != null)
|
||||
{
|
||||
clone.views = new ArrayList<>();
|
||||
for(QReportView view : this.views)
|
||||
{
|
||||
clone.views.add(view.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if(this.permissionRules != null)
|
||||
{
|
||||
clone.permissionRules = this.permissionRules.clone();
|
||||
}
|
||||
|
||||
if(this.icon != null)
|
||||
{
|
||||
clone.icon = this.icon.clone();
|
||||
}
|
||||
|
||||
if(this.exportStyleCustomizer != null)
|
||||
{
|
||||
clone.exportStyleCustomizer = this.exportStyleCustomizer.clone();
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
catch(CloneNotSupportedException e)
|
||||
{
|
||||
throw new AssertionError("Cloning not supported", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -397,4 +464,35 @@ public class QReportMetaData implements QAppChildMetaData, MetaDataWithPermissio
|
||||
qInstance.addReport(this);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for exportStyleCustomizer
|
||||
*******************************************************************************/
|
||||
public QCodeReference getExportStyleCustomizer()
|
||||
{
|
||||
return (this.exportStyleCustomizer);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for exportStyleCustomizer
|
||||
*******************************************************************************/
|
||||
public void setExportStyleCustomizer(QCodeReference exportStyleCustomizer)
|
||||
{
|
||||
this.exportStyleCustomizer = exportStyleCustomizer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for exportStyleCustomizer
|
||||
*******************************************************************************/
|
||||
public QReportMetaData withExportStyleCustomizer(QCodeReference exportStyleCustomizer)
|
||||
{
|
||||
this.exportStyleCustomizer = exportStyleCustomizer;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -24,8 +24,12 @@ package com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.StorageAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
@ -37,9 +41,14 @@ import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.mapp
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.mapping.BulkLoadTableStructureBuilder;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.model.BulkLoadFileRow;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.model.BulkLoadProfile;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.model.BulkLoadProfileField;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.model.BulkLoadTableStructure;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import org.json.JSONObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -72,7 +81,7 @@ public class BulkInsertPrepareFileMappingStep implements BackendStep
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> headerValues = (List<String>) runBackendStepOutput.getValue("headerValues");
|
||||
buildSuggestedMapping(headerValues, tableStructure, runBackendStepOutput);
|
||||
buildSuggestedMapping(headerValues, getPrepopulatedValues(runBackendStepInput), tableStructure, runBackendStepOutput);
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,10 +90,62 @@ public class BulkInsertPrepareFileMappingStep implements BackendStep
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private void buildSuggestedMapping(List<String> headerValues, BulkLoadTableStructure tableStructure, RunBackendStepOutput runBackendStepOutput)
|
||||
private Map<String, Serializable> getPrepopulatedValues(RunBackendStepInput runBackendStepInput)
|
||||
{
|
||||
String prepopulatedValuesJson = runBackendStepInput.getValueString("prepopulatedValues");
|
||||
if(StringUtils.hasContent(prepopulatedValuesJson))
|
||||
{
|
||||
Map<String, Serializable> rs = new LinkedHashMap<>();
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(prepopulatedValuesJson);
|
||||
for(String key : jsonObject.keySet())
|
||||
{
|
||||
rs.put(key, jsonObject.optString(key, null));
|
||||
}
|
||||
return (rs);
|
||||
}
|
||||
|
||||
return (Collections.emptyMap());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private void buildSuggestedMapping(List<String> headerValues, Map<String, Serializable> prepopulatedValues, BulkLoadTableStructure tableStructure, RunBackendStepOutput runBackendStepOutput)
|
||||
{
|
||||
BulkLoadMappingSuggester bulkLoadMappingSuggester = new BulkLoadMappingSuggester();
|
||||
BulkLoadProfile bulkLoadProfile = bulkLoadMappingSuggester.suggestBulkLoadMappingProfile(tableStructure, headerValues);
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(prepopulatedValues))
|
||||
{
|
||||
for(Map.Entry<String, Serializable> entry : prepopulatedValues.entrySet())
|
||||
{
|
||||
String fieldName = entry.getKey();
|
||||
boolean foundFieldInProfile = false;
|
||||
|
||||
for(BulkLoadProfileField bulkLoadProfileField : bulkLoadProfile.getFieldList())
|
||||
{
|
||||
if(bulkLoadProfileField.getFieldName().equals(fieldName))
|
||||
{
|
||||
foundFieldInProfile = true;
|
||||
bulkLoadProfileField.setColumnIndex(null);
|
||||
bulkLoadProfileField.setHeaderName(null);
|
||||
bulkLoadProfileField.setDefaultValue(entry.getValue());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!foundFieldInProfile)
|
||||
{
|
||||
BulkLoadProfileField bulkLoadProfileField = new BulkLoadProfileField();
|
||||
bulkLoadProfileField.setFieldName(fieldName);
|
||||
bulkLoadProfileField.setDefaultValue(entry.getValue());
|
||||
bulkLoadProfile.getFieldList().add(bulkLoadProfileField);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runBackendStepOutput.addValue("bulkLoadProfile", bulkLoadProfile);
|
||||
runBackendStepOutput.addValue("suggestedBulkLoadProfile", bulkLoadProfile);
|
||||
}
|
||||
|
@ -76,6 +76,23 @@ public class XlsxFileToRows extends AbstractIteratorBasedFileToRows<org.dhatim.f
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** open/go-to a specific sheet (by 0-based index). resets rows & iterator.
|
||||
***************************************************************************/
|
||||
public void openSheet(int index) throws IOException
|
||||
{
|
||||
Optional<Sheet> sheet = workbook.getSheet(index);
|
||||
|
||||
if(sheet.isEmpty())
|
||||
{
|
||||
throw (new IOException("No sheet found for index: " + index));
|
||||
}
|
||||
|
||||
rows = sheet.get().openStream();
|
||||
setIterator(rows.iterator());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
|
@ -53,7 +53,7 @@ public class BaseStreamedETLStep
|
||||
protected AbstractExtractStep getExtractStep(RunBackendStepInput runBackendStepInput)
|
||||
{
|
||||
QCodeReference codeReference = (QCodeReference) runBackendStepInput.getValue(StreamedETLWithFrontendProcess.FIELD_EXTRACT_CODE);
|
||||
return (QCodeLoader.getBackendStep(AbstractExtractStep.class, codeReference));
|
||||
return (QCodeLoader.getAdHoc(AbstractExtractStep.class, codeReference));
|
||||
}
|
||||
|
||||
|
||||
|
@ -22,13 +22,13 @@
|
||||
package com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class CouldNotFindQueryFilterForExtractStepException extends QException
|
||||
public class CouldNotFindQueryFilterForExtractStepException extends QUserFacingException
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
|
@ -279,7 +279,7 @@ public class ExtractViaQueryStep extends AbstractExtractStep
|
||||
return (new QQueryFilter().withCriteria(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, idStrings)));
|
||||
}
|
||||
|
||||
throw (new CouldNotFindQueryFilterForExtractStepException("Could not find query filter for Extract step."));
|
||||
throw (new CouldNotFindQueryFilterForExtractStepException("No records were selected for running this process."));
|
||||
}
|
||||
|
||||
|
||||
|
@ -318,7 +318,7 @@ public class SavedReportToReportMetaDataAdapter
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static QReportField makeQReportField(String fieldName, FieldAndJoinTable fieldAndJoinTable)
|
||||
public static QReportField makeQReportField(String fieldName, FieldAndJoinTable fieldAndJoinTable)
|
||||
{
|
||||
QReportField reportField = new QReportField();
|
||||
|
||||
@ -404,5 +404,5 @@ public class SavedReportToReportMetaDataAdapter
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private record FieldAndJoinTable(QFieldMetaData field, QTableMetaData joinTable) {}
|
||||
public record FieldAndJoinTable(QFieldMetaData field, QTableMetaData joinTable) {}
|
||||
}
|
||||
|
@ -22,12 +22,19 @@
|
||||
package com.kingsrook.qqq.backend.core.actions.customizers;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.InitializableViaCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReferenceWithProperties;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.Timer;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -80,6 +87,7 @@ class QCodeLoaderTest extends BaseTest
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -91,4 +99,50 @@ class QCodeLoaderTest extends BaseTest
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testCodeReferenceWithProperties()
|
||||
{
|
||||
assertNull(QCodeLoader.getAdHoc(SomeClass.class, new QCodeReference(SomeClass.class)));
|
||||
|
||||
SomeClass someObject = QCodeLoader.getAdHoc(SomeClass.class, new QCodeReferenceWithProperties(SomeClass.class, Map.of("property", "someValue")));
|
||||
assertEquals("someValue", someObject.someProperty);
|
||||
|
||||
SomeClass someOtherObject = QCodeLoader.getAdHoc(SomeClass.class, new QCodeReferenceWithProperties(SomeClass.class, Map.of("property", "someOtherValue")));
|
||||
assertEquals("someOtherValue", someOtherObject.someProperty);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static class SomeClass implements InitializableViaCodeReference
|
||||
{
|
||||
private String someProperty;
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void initialize(QCodeReference codeReference)
|
||||
{
|
||||
if(codeReference instanceof QCodeReferenceWithProperties codeReferenceWithProperties)
|
||||
{
|
||||
someProperty = ValueUtils.getValueAsString(codeReferenceWithProperties.getProperties().get("property"));
|
||||
}
|
||||
|
||||
if(!StringUtils.hasContent(someProperty))
|
||||
{
|
||||
throw new IllegalStateException("Missing property");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -364,6 +364,7 @@ class MetaDataActionTest extends BaseTest
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
@Deprecated(since = "migrated to metaDataCustomizer")
|
||||
void testFilter() throws QException
|
||||
{
|
||||
QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(MetaDataAction.class);
|
||||
@ -397,7 +398,7 @@ class MetaDataActionTest extends BaseTest
|
||||
// run again (with the same instance as before) to assert about memoization of the filter based on the QInstance //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
new MetaDataAction().execute(new MetaDataInput());
|
||||
assertThat(collectingLogger.getCollectedMessages()).filteredOn(clm -> clm.getMessage().contains("filter of type: DenyAllFilter")).hasSize(1);
|
||||
assertThat(collectingLogger.getCollectedMessages()).filteredOn(clm -> clm.getMessage().contains("actionCustomizer (via metaDataFilter reference) of type: DenyAllFilter")).hasSize(1);
|
||||
|
||||
QLogger.deactivateCollectingLoggerForClass(MetaDataAction.class);
|
||||
|
||||
@ -413,6 +414,59 @@ class MetaDataActionTest extends BaseTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testCustomizer() throws QException
|
||||
{
|
||||
QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(MetaDataAction.class);
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// run default version, and assert tables are found //
|
||||
//////////////////////////////////////////////////////
|
||||
MetaDataOutput result = new MetaDataAction().execute(new MetaDataInput());
|
||||
assertFalse(result.getTables().isEmpty(), "should be some tables");
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// run again (with the same instance as before) to assert about memoization of the filter based on the QInstance //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
new MetaDataAction().execute(new MetaDataInput());
|
||||
assertThat(collectingLogger.getCollectedMessages()).filteredOn(clm -> clm.getMessage().contains("Using new default")).hasSize(1);
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// set up new instance to use a custom filter, to deny all //
|
||||
/////////////////////////////////////////////////////////////
|
||||
QInstance instance = TestUtils.defineInstance();
|
||||
instance.setMetaDataActionCustomizer(new QCodeReference(DenyAllFilteringCustomizer.class));
|
||||
reInitInstanceInContext(instance);
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// re-run, and assert all tables are filtered away //
|
||||
/////////////////////////////////////////////////////
|
||||
result = new MetaDataAction().execute(new MetaDataInput());
|
||||
assertTrue(result.getTables().isEmpty(), "should be no tables");
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// run again (with the same instance as before) to assert about memoization of the filter based on the QInstance //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
new MetaDataAction().execute(new MetaDataInput());
|
||||
assertThat(collectingLogger.getCollectedMessages()).filteredOn(clm -> clm.getMessage().contains("meta-data actionCustomizer of type: DenyAllFilteringCustomizer")).hasSize(1);
|
||||
|
||||
QLogger.deactivateCollectingLoggerForClass(MetaDataAction.class);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// run now with the DefaultNoopMetaDataActionCustomizer, confirm we get tables //
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
instance = TestUtils.defineInstance();
|
||||
instance.setMetaDataActionCustomizer(new QCodeReference(DefaultNoopMetaDataActionCustomizer.class));
|
||||
reInitInstanceInContext(instance);
|
||||
result = new MetaDataAction().execute(new MetaDataInput());
|
||||
assertFalse(result.getTables().isEmpty(), "should be some tables");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@ -462,6 +516,67 @@ class MetaDataActionTest extends BaseTest
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public boolean allowWidget(MetaDataInput input, QWidgetMetaDataInterface widget)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static class DenyAllFilteringCustomizer implements MetaDataActionCustomizerInterface
|
||||
{
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public boolean allowTable(MetaDataInput input, QTableMetaData table)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public boolean allowProcess(MetaDataInput input, QProcessMetaData process)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public boolean allowReport(MetaDataInput input, QReportMetaData report)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public boolean allowApp(MetaDataInput input, QAppMetaData app)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
|
@ -38,6 +38,7 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.excel.TestExcelStyler;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.excel.fastexcel.ExcelFastexcelExportStreamer;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.excel.poi.BoldHeaderAndFooterPoiExcelStyler;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.excel.poi.ExcelPoiBasedStreamingExportStreamer;
|
||||
@ -56,6 +57,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria
|
||||
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.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
@ -490,6 +492,34 @@ public class GenerateReportActionTest extends BaseTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void runXlsxWithStyleCustomizer() throws Exception
|
||||
{
|
||||
ReportFormat format = ReportFormat.XLSX;
|
||||
String name = "/tmp/report-customized.xlsx";
|
||||
try(FileOutputStream fileOutputStream = new FileOutputStream(name))
|
||||
{
|
||||
QInstance qInstance = QContext.getQInstance();
|
||||
qInstance.addReport(defineTableOnlyReport());
|
||||
insertPersonRecords(qInstance);
|
||||
|
||||
ReportInput reportInput = new ReportInput();
|
||||
reportInput.setReportName(REPORT_NAME);
|
||||
reportInput.setReportDestination(new ReportDestination().withReportFormat(format).withReportOutputStream(fileOutputStream));
|
||||
reportInput.setInputValues(Map.of("startDate", LocalDate.of(1970, Month.MAY, 15), "endDate", LocalDate.now()));
|
||||
reportInput.setExportStyleCustomizer(new QCodeReference(TestExcelStyler.class));
|
||||
new GenerateReportAction().execute(reportInput);
|
||||
System.out.println("Wrote File: " + name);
|
||||
|
||||
LocalMacDevUtils.openFile(name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. 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.excel;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.excel.poi.ExcelPoiBasedStreamingStyleCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
|
||||
import org.apache.poi.ss.usermodel.CreationHelper;
|
||||
import org.apache.poi.ss.usermodel.Font;
|
||||
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class TestExcelStyler implements ExcelPoiBasedStreamingStyleCustomizerInterface
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public List<Integer> getColumnWidthsForView(QReportView view)
|
||||
{
|
||||
return List.of(60, 50, 40);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public List<String> getMergedRangesForView(QReportView view)
|
||||
{
|
||||
return List.of("A1:B1");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void customizeStyles(Map<String, XSSFCellStyle> styles, XSSFWorkbook workbook, CreationHelper createHelper)
|
||||
{
|
||||
Font font = workbook.createFont();
|
||||
font.setFontHeightInPoints((short) 16);
|
||||
font.setBold(true);
|
||||
XSSFCellStyle cellStyle = workbook.createCellStyle();
|
||||
cellStyle.setFont(font);
|
||||
styles.put("header", cellStyle);
|
||||
}
|
||||
}
|
@ -40,6 +40,7 @@ import com.kingsrook.qqq.backend.core.actions.dashboard.PersonsByCreateDateBarCh
|
||||
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer;
|
||||
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.ParentWidgetRenderer;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.AllowAllMetaDataFilter;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.DefaultNoopMetaDataActionCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.CancelProcessActionTest;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.customizers.ReportCustomRecordSourceInterface;
|
||||
@ -160,6 +161,20 @@ public class QInstanceValidatorTest extends BaseTest
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testMetaDataActionCustomizer()
|
||||
{
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.setMetaDataActionCustomizer(new QCodeReference(QInstanceValidator.class)),
|
||||
"Instance metaDataActionCustomizer CodeReference is not of the expected type");
|
||||
|
||||
assertValidationSuccess((qInstance) -> qInstance.setMetaDataActionCustomizer(new QCodeReference(DefaultNoopMetaDataActionCustomizer.class)));
|
||||
assertValidationSuccess((qInstance) -> qInstance.setMetaDataActionCustomizer(null));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Test an instance with null backends - should throw.
|
||||
|
@ -41,6 +41,7 @@ import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
@ -566,4 +567,22 @@ class QRecordEntityTest extends BaseTest
|
||||
assertEquals(0, order.getLineItems().size());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testTableName() throws QException
|
||||
{
|
||||
assertEquals(Item.TABLE_NAME, QRecordEntity.getTableName(Item.class));
|
||||
assertEquals(Item.TABLE_NAME, Item.getTableName(Item.class));
|
||||
assertEquals(Item.TABLE_NAME, new Item().tableName());
|
||||
|
||||
//////////////////////////////////
|
||||
// no TABLE_NAME in Order class //
|
||||
//////////////////////////////////
|
||||
assertThatThrownBy(() -> Order.getTableName(Order.class));
|
||||
}
|
||||
|
||||
}
|
@ -22,8 +22,22 @@
|
||||
package com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert;
|
||||
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.StorageAction;
|
||||
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.processes.RunProcessInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.storage.StorageInput;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.model.BulkLoadProfile;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.model.BulkLoadProfileField;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
|
||||
@ -66,4 +80,39 @@ class BulkInsertPrepareFileMappingStepTest extends BaseTest
|
||||
assertEquals("BAA", BulkInsertPrepareFileMappingStep.toHeaderLetter(2 * 26 * 26 + 26 + 0));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void test() throws Exception
|
||||
{
|
||||
String fileName = "personFile.csv";
|
||||
|
||||
StorageInput storageInput = new StorageInput(TestUtils.TABLE_NAME_MEMORY_STORAGE).withReference(fileName);
|
||||
OutputStream outputStream = new StorageAction().createOutputStream(storageInput);
|
||||
outputStream.write("""
|
||||
name,noOfShoes
|
||||
John,2
|
||||
Jane,4
|
||||
""".getBytes(StandardCharsets.UTF_8));
|
||||
outputStream.close();
|
||||
|
||||
RunProcessInput runProcessInput = new RunProcessInput();
|
||||
BulkInsertStepUtils.setStorageInputForTheFile(runProcessInput, storageInput);
|
||||
runProcessInput.addValue("tableName", TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
runProcessInput.addValue("prepopulatedValues", JsonUtils.toJson(Map.of("homeStateId", 1)));
|
||||
|
||||
RunBackendStepInput runBackendStepInput = new RunBackendStepInput(runProcessInput.getProcessState());
|
||||
RunBackendStepOutput runBackendStepOutput = new RunBackendStepOutput();
|
||||
|
||||
new BulkInsertPrepareFileMappingStep().run(runBackendStepInput, runBackendStepOutput);
|
||||
|
||||
BulkLoadProfile bulkLoadProfile = (BulkLoadProfile) runBackendStepOutput.getValue("suggestedBulkLoadProfile");
|
||||
Optional<BulkLoadProfileField> homeStateId = bulkLoadProfile.getFieldList().stream().filter(f -> f.getFieldName().equals("homeStateId")).findFirst();
|
||||
assertThat(homeStateId).isPresent();
|
||||
assertEquals("1", homeStateId.get().getDefaultValue());
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user