From d7bb54eb1614708efb4d1be8c0e32618a4102db7 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Tue, 8 Jul 2025 10:49:19 -0500 Subject: [PATCH] Add QHelpContentPlugin, so that supplemental instance meta data can accept help content. also add commonmark dep and getContentAsHtml method --- qqq-backend-core/pom.xml | 5 ++ .../QInstanceHelpContentManager.java | 18 ++++++ .../model/metadata/help/QHelpContent.java | 43 +++++++++++++- .../QInstanceHelpContentManagerTest.java | 58 +++++++++++++++++++ .../model/metadata/help/QHelpContentTest.java | 55 ++++++++++++++++++ 5 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/help/QHelpContentTest.java diff --git a/qqq-backend-core/pom.xml b/qqq-backend-core/pom.xml index 17fdf949..94560e8d 100644 --- a/qqq-backend-core/pom.xml +++ b/qqq-backend-core/pom.xml @@ -121,6 +121,11 @@ poi-ooxml 5.2.5 + + org.commonmark + commonmark + 0.25.0 + diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceHelpContentManager.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceHelpContentManager.java index e46fb80a..8444f6ac 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceHelpContentManager.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceHelpContentManager.java @@ -36,6 +36,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput; import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.helpcontent.HelpContent; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.QSupplementalInstanceMetaData; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.help.HelpFormat; @@ -163,6 +164,23 @@ public class QInstanceHelpContentManager { processHelpContentForInstance(qInstance, key, slotName, roles, helpContent); } + else + { + for(QSupplementalInstanceMetaData supplementalInstanceMetaData : qInstance.getSupplementalMetaData().values()) + { + if(supplementalInstanceMetaData instanceof QHelpContentPlugin helpContentPlugin) + { + try + { + helpContentPlugin.acceptHelpContent(qInstance, helpContent, nameValuePairs); + } + catch(Exception e) + { + LOG.warn("Error processing a helpContent record in a helpContentPlugin", e, logPair("pluginName", supplementalInstanceMetaData.getName()), logPair("id", record.getValue("id"))); + } + } + } + } } catch(Exception e) { diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/help/QHelpContent.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/help/QHelpContent.java index c99b9d1f..edec4192 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/help/QHelpContent.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/help/QHelpContent.java @@ -26,10 +26,13 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject; +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; /******************************************************************************* - ** meta-data defintion of "Help Content" to show to a user - for use in + ** meta-data definition of "Help Content" to show to a user - for use in ** a specific "role" (e.g., insert screens but not view screens), and in a ** particular "format" (e.g., plain text, html, markdown). ** @@ -48,6 +51,12 @@ public class QHelpContent implements QMetaDataObject private HelpFormat format; private Set roles; + //////////////////////////////////// + // these appear to be thread safe // + //////////////////////////////////// + private static Parser commonMarkParser = Parser.builder().build(); + private static HtmlRenderer commonMarkRenderer = HtmlRenderer.builder().build(); + /******************************************************************************* @@ -71,6 +80,38 @@ public class QHelpContent implements QMetaDataObject + /*************************************************************************** + * Return the content as html string, based on its format. + * Only MARKDOWN actually gets processed (via commonmark) - but TEXT and + * HTML come out as-is. + ***************************************************************************/ + public String getContentAsHtml() + { + if(content == null) + { + return (null); + } + + if(HelpFormat.MARKDOWN.equals(this.format)) + { + ////////////////////////////// + // convert markdown to HTML // + ////////////////////////////// + Node document = commonMarkParser.parse(content); + String html = commonMarkRenderer.render(document); + return (html); + } + else + { + /////////////////////////////////////////////////// + // other formats (html & text) just output as-is // + /////////////////////////////////////////////////// + return (content); + } + } + + + /******************************************************************************* ** Getter for content *******************************************************************************/ diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceHelpContentManagerTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceHelpContentManagerTest.java index 3511326f..e1bf93e2 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceHelpContentManagerTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceHelpContentManagerTest.java @@ -22,6 +22,7 @@ package com.kingsrook.qqq.backend.core.instances; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; @@ -43,6 +44,7 @@ import com.kingsrook.qqq.backend.core.model.helpcontent.HelpContent; import com.kingsrook.qqq.backend.core.model.helpcontent.HelpContentMetaDataProvider; import com.kingsrook.qqq.backend.core.model.helpcontent.HelpContentRole; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.QSupplementalInstanceMetaData; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface; import com.kingsrook.qqq.backend.core.model.metadata.help.HelpRole; import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent; @@ -477,6 +479,62 @@ class QInstanceHelpContentManagerTest extends BaseTest + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testSupplementalMetaDataHelpContentPlugin() throws QException + { + QInstance qInstance = QContext.getQInstance(); + qInstance.withSupplementalMetaData(new TestSupplementalMetaDataHelpContentPlugin()); + new HelpContentMetaDataProvider().defineAll(qInstance, TestUtils.MEMORY_BACKEND_NAME, null); + + String content = "We are the priests"; + + QInstanceHelpContentManager.processHelpContentRecord(qInstance, new HelpContent() + .withId(1) + .withKey("someContentNotOtherwiseHandled") + .withContent(content) + .withRole(HelpContentRole.INSERT_SCREEN.getId()).toQRecord()); + + assertEquals(1, TestSupplementalMetaDataHelpContentPlugin.acceptedContent.size()); + assertEquals(content, TestSupplementalMetaDataHelpContentPlugin.acceptedContent.get(0)); + } + + + + /*************************************************************************** + * + ***************************************************************************/ + public static class TestSupplementalMetaDataHelpContentPlugin implements QSupplementalInstanceMetaData, QHelpContentPlugin + { + private static List acceptedContent = new ArrayList<>(); + + + + /*************************************************************************** + * + ***************************************************************************/ + @Override + public void acceptHelpContent(QInstance qInstance, QHelpContent helpContent, Map nameValuePairs) + { + acceptedContent.add(helpContent.getContent()); + } + + + + /*************************************************************************** + * + ***************************************************************************/ + @Override + public String getName() + { + return getClass().getName(); + } + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/help/QHelpContentTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/help/QHelpContentTest.java new file mode 100644 index 00000000..f4e65e33 --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/metadata/help/QHelpContentTest.java @@ -0,0 +1,55 @@ +/* + * 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.model.metadata.help; + + +import com.kingsrook.qqq.backend.core.BaseTest; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + + +/******************************************************************************* + ** Unit test for QHelpContent + *******************************************************************************/ +class QHelpContentTest extends BaseTest +{ + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testGetContentAsHtml() + { + assertNull(new QHelpContent().withFormat(null).withContent(null).getContentAsHtml()); + assertNull(new QHelpContent().withFormat(HelpFormat.MARKDOWN).withContent(null).getContentAsHtml()); + assertNull(new QHelpContent().withFormat(HelpFormat.HTML).withContent(null).getContentAsHtml()); + assertNull(new QHelpContent().withFormat(HelpFormat.TEXT).withContent(null).getContentAsHtml()); + + assertEquals("

hi

\n", new QHelpContent().withFormat(HelpFormat.MARKDOWN).withContent("*hi*").getContentAsHtml()); + assertEquals("hi", new QHelpContent().withFormat(HelpFormat.HTML).withContent("hi").getContentAsHtml()); + assertEquals("hi", new QHelpContent().withFormat(HelpFormat.TEXT).withContent("hi").getContentAsHtml()); + assertEquals("*hi*", new QHelpContent().withFormat(HelpFormat.TEXT).withContent("*hi*").getContentAsHtml()); + assertEquals("*hi*", new QHelpContent().withFormat(null).withContent("*hi*").getContentAsHtml()); + } + +} \ No newline at end of file