From 78892b36421be22205e09447f395c9203ca4d164 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Fri, 18 Apr 2025 13:56:26 -0500 Subject: [PATCH 1/4] Fix to allow html entities by going through a w3c DOM --- .../core/actions/templates/ConvertHtmlToPdfAction.java | 4 +++- .../core/actions/templates/ConvertHtmlToPdfActionTest.java | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/templates/ConvertHtmlToPdfAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/templates/ConvertHtmlToPdfAction.java index d2dbdac1..61d97489 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/templates/ConvertHtmlToPdfAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/templates/ConvertHtmlToPdfAction.java @@ -35,6 +35,7 @@ import com.openhtmltopdf.pdfboxout.PdfBoxFontResolver; import com.openhtmltopdf.pdfboxout.PdfBoxRenderer; import com.openhtmltopdf.pdfboxout.PdfRendererBuilder; import org.jsoup.Jsoup; +import org.jsoup.helper.W3CDom; import org.jsoup.nodes.Document; @@ -67,6 +68,7 @@ public class ConvertHtmlToPdfAction extends AbstractQActionFunction

This is a test of converting HTML to PDF!!

+

This is   a line with • some entities <

(btw, is this in SF-Pro???)

From 1b9d93e92438a10a00965ed2d0576ad4ab990f2b Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Fri, 18 Apr 2025 13:57:01 -0500 Subject: [PATCH 2/4] Add CUSTOM_COMPONENT widget type --- .../qqq/backend/core/model/dashboard/widgets/WidgetType.java | 1 + 1 file changed, 1 insertion(+) diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/WidgetType.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/WidgetType.java index 9989f670..3a773a84 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/WidgetType.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/WidgetType.java @@ -66,6 +66,7 @@ public enum WidgetType // record view/edit widgets // ////////////////////////////// CHILD_RECORD_LIST("childRecordList"), + CUSTOM_COMPONENT("customComponent"), DYNAMIC_FORM("dynamicForm"), DATA_BAG_VIEWER("dataBagViewer"), PIVOT_TABLE_SETUP("pivotTableSetup"), From 97434ebb666f563cdfb443ced4dbb8fd50f80f9f Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Fri, 18 Apr 2025 13:57:43 -0500 Subject: [PATCH 3/4] Initial checkin of BasicCustomPossibleValueProvider, and migrate TablesCustomPossibleValueProvider to use it. --- .../BasicCustomPossibleValueProvider.java | 91 +++++++++++++++++++ .../TablesCustomPossibleValueProvider.java | 70 +++++++++----- ...esPossibleValueSourceMetaDataProvider.java | 2 + 3 files changed, 138 insertions(+), 25 deletions(-) create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/BasicCustomPossibleValueProvider.java diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/BasicCustomPossibleValueProvider.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/BasicCustomPossibleValueProvider.java new file mode 100644 index 00000000..da1421bc --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/BasicCustomPossibleValueProvider.java @@ -0,0 +1,91 @@ +/* + * 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 . + */ + +package com.kingsrook.qqq.backend.core.actions.values; + + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceInput; +import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue; + + +/******************************************************************************* + ** Basic implementation of a possible value provider, for where there's a limited + ** set of possible source objects - so you just have to define how to make one + ** PV from a source object, how to list all of the source objects, and how to + ** look up a PV from an id. + *******************************************************************************/ +public abstract class BasicCustomPossibleValueProvider implements QCustomPossibleValueProvider +{ + + /*************************************************************************** + ** + ***************************************************************************/ + protected abstract QPossibleValue makePossibleValue(S sourceObject); + + /*************************************************************************** + ** + ***************************************************************************/ + protected abstract S getSourceObject(Serializable id); + + /*************************************************************************** + ** + ***************************************************************************/ + protected abstract List getAllSourceObjects(); + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public QPossibleValue getPossibleValue(Serializable idValue) + { + S sourceObject = getSourceObject(idValue); + if(sourceObject == null) + { + return (null); + } + + return makePossibleValue(sourceObject); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public List> search(SearchPossibleValueSourceInput input) throws QException + { + List> allPossibleValues = new ArrayList<>(); + List allSourceObjects = getAllSourceObjects(); + for(S sourceObject : allSourceObjects) + { + allPossibleValues.add(makePossibleValue(sourceObject)); + } + + return completeCustomPVSSearch(input, allPossibleValues); + } +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/TablesCustomPossibleValueProvider.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/TablesCustomPossibleValueProvider.java index e72d188f..e2d98b4a 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/TablesCustomPossibleValueProvider.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/TablesCustomPossibleValueProvider.java @@ -27,11 +27,9 @@ import java.util.ArrayList; import java.util.List; import com.kingsrook.qqq.backend.core.actions.permissions.PermissionCheckResult; import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper; -import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider; +import com.kingsrook.qqq.backend.core.actions.values.BasicCustomPossibleValueProvider; import com.kingsrook.qqq.backend.core.context.QContext; -import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput; -import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceInput; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue; import com.kingsrook.qqq.backend.core.utils.ValueUtils; @@ -40,26 +38,16 @@ import com.kingsrook.qqq.backend.core.utils.ValueUtils; ** possible-value source provider for the `Tables` PVS - a list of all tables ** in an application/qInstance. *******************************************************************************/ -public class TablesCustomPossibleValueProvider implements QCustomPossibleValueProvider +public class TablesCustomPossibleValueProvider extends BasicCustomPossibleValueProvider { /*************************************************************************** ** ***************************************************************************/ @Override - public QPossibleValue getPossibleValue(Serializable idValue) + protected QPossibleValue makePossibleValue(QTableMetaData sourceObject) { - QTableMetaData table = QContext.getQInstance().getTable(ValueUtils.getValueAsString(idValue)); - if(table != null && !table.getIsHidden()) - { - PermissionCheckResult permissionCheckResult = PermissionsHelper.getPermissionCheckResult(new QueryInput(table.getName()), table); - if(PermissionCheckResult.ALLOW.equals(permissionCheckResult)) - { - return (new QPossibleValue<>(table.getName(), table.getLabel())); - } - } - - return null; + return (new QPossibleValue<>(sourceObject.getName(), sourceObject.getLabel())); } @@ -68,22 +56,54 @@ public class TablesCustomPossibleValueProvider implements QCustomPossibleValuePr ** ***************************************************************************/ @Override - public List> search(SearchPossibleValueSourceInput input) throws QException + protected QTableMetaData getSourceObject(Serializable id) { - ///////////////////////////////////////////////////////////////////////////////////// - // build all of the possible values (note, will be filtered by user's permissions) // - ///////////////////////////////////////////////////////////////////////////////////// - List> allPossibleValues = new ArrayList<>(); + QTableMetaData table = QContext.getQInstance().getTable(ValueUtils.getValueAsString(id)); + return isTableAllowed(table) ? table : null; + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + protected List getAllSourceObjects() + { + ArrayList rs = new ArrayList<>(); for(QTableMetaData table : QContext.getQInstance().getTables().values()) { - QPossibleValue possibleValue = getPossibleValue(table.getName()); - if(possibleValue != null) + if(isTableAllowed(table)) { - allPossibleValues.add(possibleValue); + rs.add(table); } } + return rs; + } - return completeCustomPVSSearch(input, allPossibleValues); + + /*************************************************************************** + ** + ***************************************************************************/ + private boolean isTableAllowed(QTableMetaData table) + { + if(table == null) + { + return (false); + } + + if(table.getIsHidden()) + { + return (false); + } + + PermissionCheckResult permissionCheckResult = PermissionsHelper.getPermissionCheckResult(new QueryInput(table.getName()), table); + if(!PermissionCheckResult.ALLOW.equals(permissionCheckResult)) + { + return (false); + } + + return (true); } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/TablesPossibleValueSourceMetaDataProvider.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/TablesPossibleValueSourceMetaDataProvider.java index 75087ae7..cdc9b6d3 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/TablesPossibleValueSourceMetaDataProvider.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/TablesPossibleValueSourceMetaDataProvider.java @@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.tables; 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.QFieldType; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PVSValueFormatAndFields; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType; @@ -45,6 +46,7 @@ public class TablesPossibleValueSourceMetaDataProvider { QPossibleValueSource possibleValueSource = new QPossibleValueSource() .withName(NAME) + .withIdType(QFieldType.STRING) .withType(QPossibleValueSourceType.CUSTOM) .withCustomCodeReference(new QCodeReference(TablesCustomPossibleValueProvider.class)) .withValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY); From f81b257dd44f1366b3565f4e73c62595212ad17b Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Mon, 21 Apr 2025 10:58:56 -0500 Subject: [PATCH 4/4] Improving process traces built by bulk load --- .../bulk/insert/BulkInsertExtractStep.java | 2 -- .../insert/BulkInsertPrepareFileUploadStep.java | 5 +++++ .../insert/BulkInsertReceiveFileMappingStep.java | 14 ++++++++++++-- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/bulk/insert/BulkInsertExtractStep.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/bulk/insert/BulkInsertExtractStep.java index dabfb3f4..694ae332 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/bulk/insert/BulkInsertExtractStep.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/bulk/insert/BulkInsertExtractStep.java @@ -55,8 +55,6 @@ public class BulkInsertExtractStep extends AbstractExtractStep @Override public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException { - runBackendStepInput.traceMessage(BulkInsertStepUtils.getProcessTracerKeyRecordMessage(runBackendStepInput)); - int rowsAdded = 0; int originalLimit = Objects.requireNonNullElse(getLimit(), Integer.MAX_VALUE); diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/bulk/insert/BulkInsertPrepareFileUploadStep.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/bulk/insert/BulkInsertPrepareFileUploadStep.java index ea4810a9..4b15b720 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/bulk/insert/BulkInsertPrepareFileUploadStep.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/bulk/insert/BulkInsertPrepareFileUploadStep.java @@ -52,6 +52,11 @@ public class BulkInsertPrepareFileUploadStep implements BackendStep @Override public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException { + //////////////////////////////////////////////////////////////////////////////////////// + // for headless-bulk load (e.g., sftp import), set up the process tracer's key record // + //////////////////////////////////////////////////////////////////////////////////////// + runBackendStepInput.traceMessage(BulkInsertStepUtils.getProcessTracerKeyRecordMessage(runBackendStepInput)); + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // if user has come back here, clear out file (else the storageInput object that it is comes to the frontend, which isn't what we want!) // /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/bulk/insert/BulkInsertReceiveFileMappingStep.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/bulk/insert/BulkInsertReceiveFileMappingStep.java index 94032fe5..aa460540 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/bulk/insert/BulkInsertReceiveFileMappingStep.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/bulk/insert/BulkInsertReceiveFileMappingStep.java @@ -46,6 +46,7 @@ import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.mode import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.model.BulkLoadProfileField; 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.apache.commons.lang3.BooleanUtils; @@ -77,10 +78,14 @@ public class BulkInsertReceiveFileMappingStep implements BackendStep ////////////////////////////////////////////////////////////////////////////// if(savedBulkLoadProfileRecord == null) { - throw (new QUserFacingException("Did not receive a saved bulk load profile record as input - unable to perform headless bulk load")); + throw (new QUserFacingException("Did not receive a Bulk Load Profile record as input. Unable to perform headless bulk load")); } SavedBulkLoadProfile savedBulkLoadProfile = new SavedBulkLoadProfile(savedBulkLoadProfileRecord); + if(!StringUtils.hasContent(savedBulkLoadProfile.getMappingJson())) + { + throw (new QUserFacingException("Bulk Load Profile record's Mapping is empty. Unable to perform headless bulk load")); + } try { @@ -88,7 +93,7 @@ public class BulkInsertReceiveFileMappingStep implements BackendStep } catch(Exception e) { - throw (new QUserFacingException("Error processing saved bulk load profile record - unable to perform headless bulk load", e)); + throw (new QUserFacingException("Error processing Bulk Load Profile record. Unable to perform headless bulk load", e)); } } else @@ -240,6 +245,11 @@ public class BulkInsertReceiveFileMappingStep implements BackendStep } } } + catch(QUserFacingException ufe) + { + LOG.warn("User-facing error in bulk insert receive mapping", ufe); + throw ufe; + } catch(Exception e) { LOG.warn("Error in bulk insert receive mapping", e);