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
new file mode 100644
index 00000000..739e8a74
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceHelpContentManager.java
@@ -0,0 +1,269 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2023. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.instances;
+
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
+import com.kingsrook.qqq.backend.core.logging.QLogger;
+import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
+import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
+import com.kingsrook.qqq.backend.core.model.helpcontent.HelpContent;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.help.HelpFormat;
+import com.kingsrook.qqq.backend.core.model.metadata.help.HelpRole;
+import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
+import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpRole;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
+import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
+import com.kingsrook.qqq.backend.core.utils.StringUtils;
+import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
+
+
+/*******************************************************************************
+ ** Utility methods for working with (dynamic, from a table) HelpContent - and
+ ** putting it into meta-data in a QInstance.
+ *******************************************************************************/
+public class QInstanceHelpContentManager
+{
+ private static final QLogger LOG = QLogger.getLogger(QInstanceHelpContentManager.class);
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static void loadHelpContent(QInstance qInstance)
+ {
+ try
+ {
+ if(qInstance.getTable(HelpContent.TABLE_NAME) == null)
+ {
+ return;
+ }
+
+ QueryInput queryInput = new QueryInput();
+ queryInput.setTableName(HelpContent.TABLE_NAME);
+ QueryOutput queryOutput = new QueryAction().execute(queryInput);
+ for(QRecord record : queryOutput.getRecords())
+ {
+ processHelpContentRecord(qInstance, record);
+ }
+ }
+ catch(Exception e)
+ {
+ LOG.error("Error loading help content", e);
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static void processHelpContentRecord(QInstance qInstance, QRecord record)
+ {
+ try
+ {
+ /////////////////////////////////////////////////
+ // parse the key into its parts that we expect //
+ /////////////////////////////////////////////////
+ String key = record.getValueString("key");
+ Map nameValuePairs = new HashMap<>();
+ for(String part : key.split(";"))
+ {
+ String[] parts = part.split(":");
+ nameValuePairs.put(parts[0], parts[1]);
+ }
+
+ String tableName = nameValuePairs.get("table");
+ String processName = nameValuePairs.get("process");
+ String fieldName = nameValuePairs.get("field");
+ String sectionName = nameValuePairs.get("section");
+
+ ///////////////////////////////////////////////////////////
+ // build a help content meta-data object from the record //
+ ///////////////////////////////////////////////////////////
+ QHelpContent helpContent = new QHelpContent()
+ .withContent(record.getValueString("content"))
+ .withRole(QHelpRole.valueOf(record.getValueString("role"))); // mmm, we could fall down a bit here w/ other app-defined roles...
+
+ if(StringUtils.hasContent(record.getValueString("format")))
+ {
+ helpContent.setFormat(HelpFormat.valueOf(record.getValueString("format")));
+ }
+ Set roles = helpContent.getRoles();
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////
+ // check - if there are no contents, then let's remove this help content from the container //
+ // (note pre-delete customizer will take advantage of this, passing in empty content on purpose) //
+ ///////////////////////////////////////////////////////////////////////////////////////////////////
+ if(!StringUtils.hasContent(helpContent.getContent()))
+ {
+ helpContent = null;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////
+ // look at what parts of the key we got, and find the meta-data object to update //
+ ///////////////////////////////////////////////////////////////////////////////////
+ if(StringUtils.hasContent(tableName))
+ {
+ QTableMetaData table = qInstance.getTable(tableName);
+ if(table == null)
+ {
+ LOG.info("Unrecognized table in help content", logPair("key", key));
+ return;
+ }
+
+ if(StringUtils.hasContent(fieldName))
+ {
+ //////////////////////////
+ // handle a table field //
+ //////////////////////////
+ QFieldMetaData field = table.getFields().get(fieldName);
+ if(field == null)
+ {
+ LOG.info("Unrecognized table field in help content", logPair("key", key));
+ return;
+ }
+
+ if(helpContent != null)
+ {
+ field.withHelpContent(helpContent);
+ }
+ else
+ {
+ field.removeHelpContent(roles);
+ }
+ }
+ else if(StringUtils.hasContent(sectionName))
+ {
+ ////////////////////////////
+ // handle a table section //
+ ////////////////////////////
+ Optional optionalSection = table.getSections().stream().filter(s -> sectionName.equals(s.getName())).findFirst();
+ if(optionalSection.isEmpty())
+ {
+ LOG.info("Unrecognized table section in help content", logPair("key", key));
+ return;
+ }
+
+ if(helpContent != null)
+ {
+ optionalSection.get().withHelpContent(helpContent);
+ }
+ else
+ {
+ optionalSection.get().removeHelpContent(roles);
+ }
+ }
+ }
+ else if(StringUtils.hasContent(processName))
+ {
+ QProcessMetaData process = qInstance.getProcess(processName);
+ if(process == null)
+ {
+ LOG.info("Unrecognized process in help content", logPair("key", key));
+ return;
+ }
+
+ if(StringUtils.hasContent(fieldName))
+ {
+ ////////////////////////////
+ // handle a process field //
+ ////////////////////////////
+ Optional optionalField = CollectionUtils.mergeLists(process.getInputFields(), process.getOutputFields())
+ .stream().filter(f -> fieldName.equals(f.getName()))
+ .findFirst();
+
+ if(optionalField.isEmpty())
+ {
+ LOG.info("Unrecognized process field in help content", logPair("key", key));
+ return;
+ }
+
+ if(helpContent != null)
+ {
+ optionalField.get().withHelpContent(helpContent);
+ }
+ else
+ {
+ optionalField.get().removeHelpContent(roles);
+ }
+ }
+ }
+ }
+ catch(Exception e)
+ {
+ LOG.warn("Error processing a helpContent record", e, logPair("id", record.getValue("id")));
+ }
+ }
+
+
+
+ /*******************************************************************************
+ ** add a help content object to a list - replacing an entry in the list with the
+ ** same roles if one is found.
+ *******************************************************************************/
+ public static void putHelpContentInList(QHelpContent helpContent, List helpContents)
+ {
+ ListIterator iterator = helpContents.listIterator();
+ while(iterator.hasNext())
+ {
+ QHelpContent existingContent = iterator.next();
+ if(Objects.equals(existingContent.getRoles(), helpContent.getRoles()))
+ {
+ iterator.set(helpContent);
+ return;
+ }
+ }
+
+ helpContents.add(helpContent);
+ }
+
+
+
+ /*******************************************************************************
+ ** Remove any helpContent entries in a list if they have a set of roles that matches
+ ** the input set.
+ *******************************************************************************/
+ public static void removeHelpContentByRoleSetFromList(Set roles, List helpContents)
+ {
+ if(helpContents == null)
+ {
+ return;
+ }
+
+ helpContents.removeIf(existingContent -> Objects.equals(existingContent.getRoles(), roles));
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/helpcontent/HelpContent.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/helpcontent/HelpContent.java
new file mode 100644
index 00000000..93106d83
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/helpcontent/HelpContent.java
@@ -0,0 +1,296 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2023. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.helpcontent;
+
+
+import java.time.Instant;
+import com.kingsrook.qqq.backend.core.model.data.QField;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
+import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
+import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
+
+
+/*******************************************************************************
+ ** QRecord Entity for HelpContent table
+ *******************************************************************************/
+public class HelpContent extends QRecordEntity
+{
+ public static final String TABLE_NAME = "helpContent";
+
+ @QField(isEditable = false)
+ private Integer id;
+
+ @QField(isEditable = false)
+ private Instant createDate;
+
+ @QField(isEditable = false)
+ private Instant modifyDate;
+
+ @QField(isRequired = true, maxLength = 250, valueTooLongBehavior = ValueTooLongBehavior.ERROR)
+ private String key;
+
+ @QField()
+ private String content;
+
+ @QField(possibleValueSourceName = HelpContentFormat.NAME)
+ private String format;
+
+ @QField(possibleValueSourceName = HelpContentRole.NAME, isRequired = true)
+ private String role;
+
+
+
+ /*******************************************************************************
+ ** Default constructor
+ *******************************************************************************/
+ public HelpContent()
+ {
+ }
+
+
+
+ /*******************************************************************************
+ ** Constructor that takes a QRecord
+ *******************************************************************************/
+ public HelpContent(QRecord record)
+ {
+ populateFromQRecord(record);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for id
+ *******************************************************************************/
+ public Integer getId()
+ {
+ return (this.id);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for id
+ *******************************************************************************/
+ public void setId(Integer id)
+ {
+ this.id = id;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for id
+ *******************************************************************************/
+ public HelpContent withId(Integer id)
+ {
+ this.id = id;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for createDate
+ *******************************************************************************/
+ public Instant getCreateDate()
+ {
+ return (this.createDate);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for createDate
+ *******************************************************************************/
+ public void setCreateDate(Instant createDate)
+ {
+ this.createDate = createDate;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for createDate
+ *******************************************************************************/
+ public HelpContent withCreateDate(Instant createDate)
+ {
+ this.createDate = createDate;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for modifyDate
+ *******************************************************************************/
+ public Instant getModifyDate()
+ {
+ return (this.modifyDate);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for modifyDate
+ *******************************************************************************/
+ public void setModifyDate(Instant modifyDate)
+ {
+ this.modifyDate = modifyDate;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for modifyDate
+ *******************************************************************************/
+ public HelpContent withModifyDate(Instant modifyDate)
+ {
+ this.modifyDate = modifyDate;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for key
+ *******************************************************************************/
+ public String getKey()
+ {
+ return (this.key);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for key
+ *******************************************************************************/
+ public void setKey(String key)
+ {
+ this.key = key;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for key
+ *******************************************************************************/
+ public HelpContent withKey(String key)
+ {
+ this.key = key;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for content
+ *******************************************************************************/
+ public String getContent()
+ {
+ return (this.content);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for content
+ *******************************************************************************/
+ public void setContent(String content)
+ {
+ this.content = content;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for content
+ *******************************************************************************/
+ public HelpContent withContent(String content)
+ {
+ this.content = content;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for format
+ *******************************************************************************/
+ public String getFormat()
+ {
+ return (this.format);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for format
+ *******************************************************************************/
+ public void setFormat(String format)
+ {
+ this.format = format;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for format
+ *******************************************************************************/
+ public HelpContent withFormat(String format)
+ {
+ this.format = format;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for role
+ *******************************************************************************/
+ public String getRole()
+ {
+ return (this.role);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for role
+ *******************************************************************************/
+ public void setRole(String role)
+ {
+ this.role = role;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for role
+ *******************************************************************************/
+ public HelpContent withRole(String role)
+ {
+ this.role = role;
+ return (this);
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/helpcontent/HelpContentFormat.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/helpcontent/HelpContentFormat.java
new file mode 100644
index 00000000..73f1c127
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/helpcontent/HelpContentFormat.java
@@ -0,0 +1,121 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2023. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.helpcontent;
+
+
+import java.util.Objects;
+import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PossibleValueEnum;
+
+
+/*******************************************************************************
+ ** HelpContentFormat - possible value enum
+ *******************************************************************************/
+public enum HelpContentFormat implements PossibleValueEnum
+{
+ TEXT("TEXT", "Plain Text"),
+ HTML("HTML", "HTML"),
+ MARKDOWN("MARKDOWN", "Markdown");
+
+ private final String id;
+ private final String label;
+
+ public static final String NAME = "helpContentFormat";
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ HelpContentFormat(String id, String label)
+ {
+ this.id = id;
+ this.label = label;
+ }
+
+
+
+ /*******************************************************************************
+ ** Get instance by id
+ **
+ *******************************************************************************/
+ public static HelpContentFormat getById(String id)
+ {
+ if(id == null)
+ {
+ return (null);
+ }
+
+ for(HelpContentFormat value : HelpContentFormat.values())
+ {
+ if(Objects.equals(value.id, id))
+ {
+ return (value);
+ }
+ }
+
+ return (null);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for id
+ **
+ *******************************************************************************/
+ public String getId()
+ {
+ return id;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for label
+ **
+ *******************************************************************************/
+ public String getLabel()
+ {
+ return label;
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public String getPossibleValueId()
+ {
+ return (getId());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public String getPossibleValueLabel()
+ {
+ return (getLabel());
+ }
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/helpcontent/HelpContentMetaDataProvider.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/helpcontent/HelpContentMetaDataProvider.java
new file mode 100644
index 00000000..45657a3c
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/helpcontent/HelpContentMetaDataProvider.java
@@ -0,0 +1,94 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2023. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.helpcontent;
+
+
+import java.util.List;
+import java.util.function.Consumer;
+import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+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.AdornmentType;
+import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
+import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
+import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
+
+
+/*******************************************************************************
+ ** Meta-data provider for table & PVS's for defining help-content for other
+ ** meta-data objects within a QQQ app
+ *******************************************************************************/
+public class HelpContentMetaDataProvider
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public void defineAll(QInstance instance, String backendName, Consumer backendDetailEnricher) throws QException
+ {
+ defineHelpContentTable(instance, backendName, backendDetailEnricher);
+ instance.addPossibleValueSource(QPossibleValueSource.newForEnum(HelpContentFormat.NAME, HelpContentFormat.values()));
+ instance.addPossibleValueSource(QPossibleValueSource.newForEnum(HelpContentRole.NAME, HelpContentRole.values()));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private void defineHelpContentTable(QInstance instance, String backendName, Consumer backendDetailEnricher) throws QException
+ {
+ QTableMetaData table = new QTableMetaData()
+ .withName(HelpContent.TABLE_NAME)
+ .withBackendName(backendName)
+ .withRecordLabelFormat("%s %s")
+ .withRecordLabelFields("key", "role")
+ .withPrimaryKeyField("id")
+ .withUniqueKey(new UniqueKey("key", "role"))
+ .withFieldsFromEntity(HelpContent.class)
+ .withSection(new QFieldSection("identity", new QIcon("badge"), Tier.T1, List.of("id", "key", "role")))
+ .withSection(new QFieldSection("content", new QIcon("dataset"), Tier.T2, List.of("format", "content")))
+ .withSection(new QFieldSection("dates", new QIcon("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")))
+ .withCustomizer(TableCustomizers.POST_INSERT_RECORD, new QCodeReference(HelpContentPostInsertCustomizer.class))
+ .withCustomizer(TableCustomizers.POST_UPDATE_RECORD, new QCodeReference(HelpContentPostUpdateCustomizer.class))
+ .withCustomizer(TableCustomizers.PRE_UPDATE_RECORD, new QCodeReference(HelpContentPreUpdateCustomizer.class))
+ .withCustomizer(TableCustomizers.PRE_DELETE_RECORD, new QCodeReference(HelpContentPreDeleteCustomizer.class));
+
+ table.getField("format").withFieldAdornment(AdornmentType.Size.SMALL.toAdornment());
+ table.getField("key").withFieldAdornment(AdornmentType.Size.LARGE.toAdornment());
+ table.getField("content").withFieldAdornment(AdornmentType.Size.LARGE.toAdornment());
+ table.getField("content").withFieldAdornment(new FieldAdornment(AdornmentType.CODE_EDITOR).withValue(AdornmentType.CodeEditorValues.languageMode("html")));
+
+ if(backendDetailEnricher != null)
+ {
+ backendDetailEnricher.accept(table);
+ }
+
+ instance.addTable(table);
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/helpcontent/HelpContentPostInsertCustomizer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/helpcontent/HelpContentPostInsertCustomizer.java
new file mode 100644
index 00000000..bf889a7f
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/helpcontent/HelpContentPostInsertCustomizer.java
@@ -0,0 +1,66 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2023. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.helpcontent;
+
+
+import java.util.List;
+import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostInsertCustomizer;
+import com.kingsrook.qqq.backend.core.context.QContext;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.instances.QInstanceHelpContentManager;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
+
+
+/*******************************************************************************
+ ** after records are inserted, put their help content in meta-data
+ *******************************************************************************/
+public class HelpContentPostInsertCustomizer extends AbstractPostInsertCustomizer
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public List apply(List records) throws QException
+ {
+ return insertRecordsIntoMetaData(records);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ static List insertRecordsIntoMetaData(List records)
+ {
+ if(records != null)
+ {
+ for(QRecord record : records)
+ {
+ QInstanceHelpContentManager.processHelpContentRecord(QContext.getQInstance(), record);
+ }
+ }
+
+ return records;
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/helpcontent/HelpContentPostUpdateCustomizer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/helpcontent/HelpContentPostUpdateCustomizer.java
new file mode 100644
index 00000000..cceaccb5
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/helpcontent/HelpContentPostUpdateCustomizer.java
@@ -0,0 +1,46 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2023. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.helpcontent;
+
+
+import java.util.List;
+import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostUpdateCustomizer;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
+
+
+/*******************************************************************************
+ ** after records are updated, put their help content in meta-data
+ *******************************************************************************/
+public class HelpContentPostUpdateCustomizer extends AbstractPostUpdateCustomizer
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public List apply(List records) throws QException
+ {
+ return HelpContentPostInsertCustomizer.insertRecordsIntoMetaData(records);
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/helpcontent/HelpContentPreDeleteCustomizer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/helpcontent/HelpContentPreDeleteCustomizer.java
new file mode 100644
index 00000000..22b63ecf
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/helpcontent/HelpContentPreDeleteCustomizer.java
@@ -0,0 +1,73 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2023. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.helpcontent;
+
+
+import java.util.List;
+import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreDeleteCustomizer;
+import com.kingsrook.qqq.backend.core.context.QContext;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.instances.QInstanceHelpContentManager;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
+
+
+/*******************************************************************************
+ ** remove existing helpContent from meta-data when a record is deleted
+ *******************************************************************************/
+public class HelpContentPreDeleteCustomizer extends AbstractPreDeleteCustomizer
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public List apply(List records) throws QException
+ {
+ if(records != null)
+ {
+ for(QRecord record : records)
+ {
+ removeOldRecordFromMetaData(record);
+ }
+ }
+
+ return (records);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ static void removeOldRecordFromMetaData(QRecord oldRecord)
+ {
+ ////////////////////////////////////////////////////////////////////////////
+ // this (clearing the content) will remove the helpContent under this key //
+ ////////////////////////////////////////////////////////////////////////////
+ if(oldRecord != null)
+ {
+ QRecord recordWithoutContent = new QRecord(oldRecord);
+ recordWithoutContent.setValue("content", null);
+ QInstanceHelpContentManager.processHelpContentRecord(QContext.getQInstance(), recordWithoutContent);
+ }
+ }
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/helpcontent/HelpContentPreUpdateCustomizer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/helpcontent/HelpContentPreUpdateCustomizer.java
new file mode 100644
index 00000000..64851dd5
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/helpcontent/HelpContentPreUpdateCustomizer.java
@@ -0,0 +1,55 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2023. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.helpcontent;
+
+
+import java.util.List;
+import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreUpdateCustomizer;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
+
+
+/*******************************************************************************
+ ** in case a row's Key or Role was changed, remove existing helpContent from that key.
+ *******************************************************************************/
+public class HelpContentPreUpdateCustomizer extends AbstractPreUpdateCustomizer
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public List apply(List records) throws QException
+ {
+ if(records != null)
+ {
+ for(QRecord record : records)
+ {
+ QRecord oldRecord = getOldRecordMap().get(record.getValueInteger("id"));
+ HelpContentPreDeleteCustomizer.removeOldRecordFromMetaData(oldRecord);
+ }
+ }
+
+ return (records);
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/helpcontent/HelpContentRole.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/helpcontent/HelpContentRole.java
new file mode 100644
index 00000000..ffdddfd1
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/helpcontent/HelpContentRole.java
@@ -0,0 +1,128 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2023. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.helpcontent;
+
+
+import java.util.Objects;
+import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpRole;
+import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PossibleValueEnum;
+
+
+/*******************************************************************************
+ ** HelpContentRole - possible value enum
+ *******************************************************************************/
+public enum HelpContentRole implements PossibleValueEnum
+{
+ ALL_SCREENS(QHelpRole.ALL_SCREENS.name(), "All Screens"),
+ READ_SCREENS(QHelpRole.READ_SCREENS.name(), "Query & View Screens"),
+ WRITE_SCREENS(QHelpRole.WRITE_SCREENS.name(), "Insert & Edit Screens"),
+ QUERY_SCREEN(QHelpRole.QUERY_SCREEN.name(), "Query Screen Only"),
+ VIEW_SCREEN(QHelpRole.VIEW_SCREEN.name(), "View Screen Only"),
+ EDIT_SCREEN(QHelpRole.EDIT_SCREEN.name(), "Edit Screen Only"),
+ INSERT_SCREEN(QHelpRole.INSERT_SCREEN.name(), "Insert Screen Only"),
+ PROCESS_SCREEN(QHelpRole.PROCESS_SCREEN.name(), "Process Screens");
+
+
+ private final String id;
+ private final String label;
+
+ public static final String NAME = "HelpContentRole";
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ HelpContentRole(String id, String label)
+ {
+ this.id = id;
+ this.label = label;
+ }
+
+
+
+ /*******************************************************************************
+ ** Get instance by id
+ **
+ *******************************************************************************/
+ public static HelpContentRole getById(String id)
+ {
+ if(id == null)
+ {
+ return (null);
+ }
+
+ for(HelpContentRole value : HelpContentRole.values())
+ {
+ if(Objects.equals(value.id, id))
+ {
+ return (value);
+ }
+ }
+
+ return (null);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for id
+ **
+ *******************************************************************************/
+ public String getId()
+ {
+ return id;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for label
+ **
+ *******************************************************************************/
+ public String getLabel()
+ {
+ return label;
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public String getPossibleValueId()
+ {
+ return (getId());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public String getPossibleValueLabel()
+ {
+ return (getLabel());
+ }
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/QFieldMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/QFieldMetaData.java
index 44d023d5..6bbdb6cd 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/QFieldMetaData.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/QFieldMetaData.java
@@ -34,10 +34,13 @@ import java.util.Set;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.github.hervian.reflection.Fun;
import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.instances.QInstanceHelpContentManager;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.metadata.help.HelpRole;
+import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
import com.kingsrook.qqq.backend.core.model.metadata.security.FieldSecurityLock;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
@@ -65,7 +68,7 @@ public class QFieldMetaData implements Cloneable
// propose doing that in a secondary field, e.g., "onlyEditableOn=insert|update" //
///////////////////////////////////////////////////////////////////////////////////
- private String displayFormat = "%s";
+ private String displayFormat = "%s";
private Serializable defaultValue;
private String possibleValueSourceName;
private QQueryFilter possibleValueSourceFilter;
@@ -84,6 +87,7 @@ public class QFieldMetaData implements Cloneable
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private List adornments;
+ private List helpContents;
private Map supplementalMetaData;
@@ -928,4 +932,61 @@ public class QFieldMetaData implements Cloneable
return (this);
}
+
+
+ /*******************************************************************************
+ ** Getter for helpContents
+ *******************************************************************************/
+ public List getHelpContents()
+ {
+ return (this.helpContents);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for helpContents
+ *******************************************************************************/
+ public void setHelpContents(List helpContents)
+ {
+ this.helpContents = helpContents;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for helpContents
+ *******************************************************************************/
+ public QFieldMetaData withHelpContents(List helpContents)
+ {
+ this.helpContents = helpContents;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for adding 1 helpContent
+ *******************************************************************************/
+ public QFieldMetaData withHelpContent(QHelpContent helpContent)
+ {
+ if(this.helpContents == null)
+ {
+ this.helpContents = new ArrayList<>();
+ }
+
+ QInstanceHelpContentManager.putHelpContentInList(helpContent, this.helpContents);
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** remove a single helpContent based on its set of roles
+ *******************************************************************************/
+ public void removeHelpContent(Set roles)
+ {
+ QInstanceHelpContentManager.removeHelpContentByRoleSetFromList(roles, this.helpContents);
+ }
+
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendFieldMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendFieldMetaData.java
index c8c76555..dbcf52b7 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendFieldMetaData.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendFieldMetaData.java
@@ -29,6 +29,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
+import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
/*******************************************************************************
@@ -50,6 +51,7 @@ public class QFrontendFieldMetaData
private Serializable defaultValue;
private List adornments;
+ private List helpContents;
//////////////////////////////////////////////////////////////////////////////////
// do not add setters. take values from the source-object in the constructor!! //
@@ -72,6 +74,7 @@ public class QFrontendFieldMetaData
this.displayFormat = fieldMetaData.getDisplayFormat();
this.adornments = fieldMetaData.getAdornments();
this.defaultValue = fieldMetaData.getDefaultValue();
+ this.helpContents = fieldMetaData.getHelpContents();
}
@@ -183,4 +186,16 @@ public class QFrontendFieldMetaData
{
return defaultValue;
}
+
+
+
+ /*******************************************************************************
+ ** Getter for helpContents
+ **
+ *******************************************************************************/
+ public List getHelpContents()
+ {
+ return helpContents;
+ }
+
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/help/HelpFormat.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/help/HelpFormat.java
new file mode 100644
index 00000000..7490a96d
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/help/HelpFormat.java
@@ -0,0 +1,33 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2023. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.metadata.help;
+
+
+/*******************************************************************************
+ ** How a piece of help content is formatted.
+ *******************************************************************************/
+public enum HelpFormat
+{
+ TEXT,
+ HTML,
+ MARKDOWN
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/help/HelpRole.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/help/HelpRole.java
new file mode 100644
index 00000000..2fefa1eb
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/help/HelpRole.java
@@ -0,0 +1,35 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2023. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.metadata.help;
+
+
+/*******************************************************************************
+ ** Interface to be associated with a HelpContent, to identify where the content
+ ** is meant to be used (e.g., only on "write" screens, vs. on app home pages, etc).
+ **
+ ** Defined in HelpContext to be this interface, so alternate frontends can
+ ** specify their own particular values - but a standard set of values is provided
+ ** by QQQ in QHelpRole.
+ *******************************************************************************/
+public interface HelpRole
+{
+}
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
new file mode 100644
index 00000000..f7bb173d
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/help/QHelpContent.java
@@ -0,0 +1,232 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2023. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.metadata.help;
+
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+
+/*******************************************************************************
+ ** meta-data defintion 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).
+ **
+ ** Meant to be assigned to several different pieces of QQQ meta data (fields,
+ ** tables, processes, etc), and used as-needed by various frontends.
+ **
+ ** May evolve something like a "Presentation" attribute in the future - e.g.,
+ ** to say "present this one as a tooltip" vs. "present this one as inline text"
+ **
+ ** May be dynamically added to meta-data via (non-meta-) data - see
+ ** HelpContentMetaDataProvider and QInstanceHelpContentManager
+ *******************************************************************************/
+public class QHelpContent
+{
+ private String content;
+ private HelpFormat format;
+ private Set roles;
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public QHelpContent()
+ {
+ }
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public QHelpContent(String content)
+ {
+ setContent(content);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for content
+ *******************************************************************************/
+ public String getContent()
+ {
+ return (this.content);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for content
+ *******************************************************************************/
+ public void setContent(String content)
+ {
+ this.content = content;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for content
+ *******************************************************************************/
+ public QHelpContent withContent(String content)
+ {
+ this.content = content;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for content that also sets format as HTML
+ *******************************************************************************/
+ public QHelpContent withContentAsHTML(String content)
+ {
+ this.content = content;
+ this.format = HelpFormat.HTML;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for content that also sets format as TEXT
+ *******************************************************************************/
+ public QHelpContent withContentAsText(String content)
+ {
+ this.content = content;
+ this.format = HelpFormat.TEXT;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for content that also sets format as Markdown
+ *******************************************************************************/
+ public QHelpContent withContentAsMarkdown(String content)
+ {
+ this.content = content;
+ this.format = HelpFormat.MARKDOWN;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for format
+ *******************************************************************************/
+ public HelpFormat getFormat()
+ {
+ return (this.format);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for format
+ *******************************************************************************/
+ public void setFormat(HelpFormat format)
+ {
+ this.format = format;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for format
+ *******************************************************************************/
+ public QHelpContent withFormat(HelpFormat format)
+ {
+ this.format = format;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for roles
+ *******************************************************************************/
+ public Set getRoles()
+ {
+ return (this.roles);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for roles
+ *******************************************************************************/
+ public void setRoles(Set roles)
+ {
+ this.roles = roles;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for roles
+ *******************************************************************************/
+ public QHelpContent withRoles(Set roles)
+ {
+ this.roles = roles;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent method to add a role
+ *******************************************************************************/
+ public QHelpContent withRole(HelpRole role)
+ {
+ return (withRoles(role));
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent method to add a role
+ *******************************************************************************/
+ public QHelpContent withRoles(HelpRole... roles)
+ {
+ if(roles == null || roles.length == 0)
+ {
+ return (this);
+ }
+
+ if(this.roles == null)
+ {
+ this.roles = new HashSet<>();
+ }
+
+ Collections.addAll(this.roles, roles);
+
+ return (this);
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/help/QHelpRole.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/help/QHelpRole.java
new file mode 100644
index 00000000..66f2e0ce
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/help/QHelpRole.java
@@ -0,0 +1,40 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2023. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.metadata.help;
+
+
+/*******************************************************************************
+ ** QQQ default or standard HelpRoles.
+ *******************************************************************************/
+public enum QHelpRole implements HelpRole
+{
+ ALL_SCREENS,
+ READ_SCREENS,
+ WRITE_SCREENS,
+ QUERY_SCREEN,
+ VIEW_SCREEN,
+ EDIT_SCREEN,
+ INSERT_SCREEN,
+ PROCESS_SCREEN,
+ APP_SCREEN,
+ TABLE_ACTION_MENU
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QFieldSection.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QFieldSection.java
index d0facc65..157d01db 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QFieldSection.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QFieldSection.java
@@ -22,7 +22,12 @@
package com.kingsrook.qqq.backend.core.model.metadata.tables;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
+import com.kingsrook.qqq.backend.core.instances.QInstanceHelpContentManager;
+import com.kingsrook.qqq.backend.core.model.metadata.help.HelpRole;
+import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.utils.collections.MutableList;
@@ -44,6 +49,8 @@ public class QFieldSection
private boolean isHidden = false;
private Integer gridColumns;
+ private List helpContents;
+
/*******************************************************************************
@@ -364,4 +371,61 @@ public class QFieldSection
return (this);
}
+
+
+ /*******************************************************************************
+ ** Getter for helpContents
+ *******************************************************************************/
+ public List getHelpContents()
+ {
+ return (this.helpContents);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for helpContents
+ *******************************************************************************/
+ public void setHelpContents(List helpContents)
+ {
+ this.helpContents = helpContents;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for helpContents
+ *******************************************************************************/
+ public QFieldSection withHelpContents(List helpContents)
+ {
+ this.helpContents = helpContents;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for adding 1 helpContent
+ *******************************************************************************/
+ public QFieldSection withHelpContent(QHelpContent helpContent)
+ {
+ if(this.helpContents == null)
+ {
+ this.helpContents = new ArrayList<>();
+ }
+
+ QInstanceHelpContentManager.putHelpContentInList(helpContent, this.helpContents);
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** remove a single helpContent based on its set of roles
+ *******************************************************************************/
+ public void removeHelpContent(Set roles)
+ {
+ QInstanceHelpContentManager.removeHelpContentByRoleSetFromList(roles, this.helpContents);
+ }
+
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/general/StandardProcessSummaryLineProducer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/general/StandardProcessSummaryLineProducer.java
index 14bf976c..54093f20 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/general/StandardProcessSummaryLineProducer.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/general/StandardProcessSummaryLineProducer.java
@@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.processes.implementations.general;
import java.util.ArrayList;
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLine;
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLineInterface;
+import com.kingsrook.qqq.backend.core.model.actions.processes.Status;
import static com.kingsrook.qqq.backend.core.model.actions.processes.Status.ERROR;
import static com.kingsrook.qqq.backend.core.model.actions.processes.Status.OK;
@@ -80,6 +81,20 @@ public class StandardProcessSummaryLineProducer
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static ProcessSummaryLine getNoDifferencesNoUpdateLine()
+ {
+ return new ProcessSummaryLine(Status.INFO)
+ .withSingularFutureMessage("has no differences and will not be updated")
+ .withPluralFutureMessage("have no differences and will not be updated")
+ .withSingularPastMessage("has no differences and was not updated")
+ .withPluralPastMessage("have no differences and were not updated");
+ }
+
+
+
/*******************************************************************************
** Make a line that'll say " had an error"
*******************************************************************************/
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
new file mode 100644
index 00000000..123c6392
--- /dev/null
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceHelpContentManagerTest.java
@@ -0,0 +1,298 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2023. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.instances;
+
+
+import java.util.List;
+import java.util.Set;
+import com.kingsrook.qqq.backend.core.BaseTest;
+import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
+import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
+import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
+import com.kingsrook.qqq.backend.core.context.QContext;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
+import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
+import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
+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.help.HelpRole;
+import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
+import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpRole;
+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;
+
+
+/*******************************************************************************
+ ** Unit test for QInstanceHelpContentManager
+ *******************************************************************************/
+class QInstanceHelpContentManagerTest extends BaseTest
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testTableField() throws QException
+ {
+ /////////////////////////////////////
+ // get the instance from base test //
+ /////////////////////////////////////
+ QInstance qInstance = QContext.getQInstance();
+ new HelpContentMetaDataProvider().defineAll(qInstance, TestUtils.MEMORY_BACKEND_NAME, null);
+
+ ////////////////////////////////////////////////////////
+ // first, assert there's no help content on person.id //
+ ////////////////////////////////////////////////////////
+ assertNoPersonIdHelp(qInstance);
+
+ HelpContent recordEntity = new HelpContent()
+ .withId(1)
+ .withKey("table:person;field:id")
+ .withContent("v1")
+ .withRole(HelpContentRole.INSERT_SCREEN.getId());
+ new InsertAction().execute(new InsertInput(HelpContent.TABLE_NAME).withRecordEntity(recordEntity));
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // now - post-insert customizer should have automatically added help content to the instance //
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ assertOnePersonIdHelp(qInstance, "v1", Set.of(QHelpRole.INSERT_SCREEN));
+
+ ///////////////////////////////////////////////////
+ // define a new instance - assert is empty again //
+ ///////////////////////////////////////////////////
+ QInstance newInstance = TestUtils.defineInstance();
+ QContext.setQInstance(newInstance);
+ new HelpContentMetaDataProvider().defineAll(newInstance, TestUtils.MEMORY_BACKEND_NAME, null);
+ assertNoPersonIdHelp(newInstance);
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // now run the method that start-up (or hotswap) will run, to look up existing records and translate to meta-data //
+ // then re-assert that the help is back //
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ QInstanceHelpContentManager.loadHelpContent(newInstance);
+ assertOnePersonIdHelp(newInstance, "v1", Set.of(QHelpRole.INSERT_SCREEN));
+
+ ////////////////////////////////////////////////////////////////////
+ // update the record's content - the meta-data should get updated //
+ ////////////////////////////////////////////////////////////////////
+ recordEntity.setContent("v2");
+ new UpdateAction().execute(new UpdateInput(HelpContent.TABLE_NAME).withRecordEntity(recordEntity));
+ assertOnePersonIdHelp(newInstance, "v2", Set.of(QHelpRole.INSERT_SCREEN));
+
+ ////////////////////////////////////////////////////////////////////////////
+ // now update the role and assert it "moves" in the meta-data as expected //
+ ////////////////////////////////////////////////////////////////////////////
+ recordEntity.setRole(HelpContentRole.WRITE_SCREENS.getId());
+ new UpdateAction().execute(new UpdateInput(HelpContent.TABLE_NAME).withRecordEntity(recordEntity));
+ assertOnePersonIdHelp(newInstance, "v2", Set.of(QHelpRole.WRITE_SCREENS));
+
+ //////////////////////////////////////////////////////////////////////////////////////
+ // now delete the record - the pre-insert should remove the help from the meta-data //
+ //////////////////////////////////////////////////////////////////////////////////////
+ new DeleteAction().execute(new DeleteInput(HelpContent.TABLE_NAME).withPrimaryKeys(List.of(1)));
+ assertNoPersonIdHelp(newInstance);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testTableSection() throws QException
+ {
+ /////////////////////////////////////
+ // get the instance from base test //
+ /////////////////////////////////////
+ QInstance qInstance = QContext.getQInstance();
+ new HelpContentMetaDataProvider().defineAll(qInstance, TestUtils.MEMORY_BACKEND_NAME, null);
+
+ //////////////////////////////////////////////////////////
+ // first, assert there's no help content on the section //
+ //////////////////////////////////////////////////////////
+ assertNoPersonSectionHelp(qInstance);
+
+ HelpContent recordEntity = new HelpContent()
+ .withId(1)
+ .withKey("table:person;section:identity")
+ .withContent("v1")
+ .withRole(HelpContentRole.INSERT_SCREEN.getId());
+ new InsertAction().execute(new InsertInput(HelpContent.TABLE_NAME).withRecordEntity(recordEntity));
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // now - post-insert customizer should have automatically added help content to the instance //
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ assertOnePersonSectionHelp(qInstance, "v1", Set.of(QHelpRole.INSERT_SCREEN));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testProcessField() throws QException
+ {
+ /////////////////////////////////////
+ // get the instance from base test //
+ /////////////////////////////////////
+ QInstance qInstance = QContext.getQInstance();
+ new HelpContentMetaDataProvider().defineAll(qInstance, TestUtils.MEMORY_BACKEND_NAME, null);
+
+ //////////////////////////////////////////////////////////
+ // first, assert there's no help content on the section //
+ //////////////////////////////////////////////////////////
+ assertNoGreetPersonFieldHelp(qInstance);
+
+ HelpContent recordEntity = new HelpContent()
+ .withId(1)
+ .withKey("process:" + TestUtils.PROCESS_NAME_GREET_PEOPLE + ";field:greetingPrefix")
+ .withContent("v1")
+ .withRole(HelpContentRole.INSERT_SCREEN.getId());
+ new InsertAction().execute(new InsertInput(HelpContent.TABLE_NAME).withRecordEntity(recordEntity));
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // now - post-insert customizer should have automatically added help content to the instance //
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ assertOneGreetPersonFieldHelp(qInstance, "v1", Set.of(QHelpRole.INSERT_SCREEN));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testInsertedRecordReplacesHelpContentFromMetaData() throws QException
+ {
+ /////////////////////////////////////
+ // get the instance from base test //
+ /////////////////////////////////////
+ QInstance qInstance = QContext.getQInstance();
+ qInstance.getTable(TestUtils.TABLE_NAME_PERSON).getField("id")
+ .withHelpContent(new QHelpContent().withContent("v0").withRole(QHelpRole.INSERT_SCREEN));
+ new HelpContentMetaDataProvider().defineAll(qInstance, TestUtils.MEMORY_BACKEND_NAME, null);
+
+ /////////////////////////////////////////////
+ // assert the help from meta-data is there //
+ /////////////////////////////////////////////
+ assertOnePersonIdHelp(qInstance, "v0", Set.of(QHelpRole.INSERT_SCREEN));
+
+ HelpContent recordEntity = new HelpContent()
+ .withId(1)
+ .withKey("table:person;field:id")
+ .withContent("v1")
+ .withRole(HelpContentRole.INSERT_SCREEN.getId());
+ new InsertAction().execute(new InsertInput(HelpContent.TABLE_NAME).withRecordEntity(recordEntity));
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // now - post-insert customizer should have automatically added help content to the instance //
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ assertOnePersonIdHelp(qInstance, "v1", Set.of(QHelpRole.INSERT_SCREEN));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ void assertNoPersonIdHelp(QInstance qInstance)
+ {
+ List helpContents = qInstance.getTable(TestUtils.TABLE_NAME_PERSON).getField("id").getHelpContents();
+ assertThat(helpContents).isNullOrEmpty();
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ void assertOnePersonIdHelp(QInstance qInstance, String content, Set roles)
+ {
+ List helpContents = qInstance.getTable(TestUtils.TABLE_NAME_PERSON).getField("id").getHelpContents();
+ assertEquals(1, helpContents.size());
+ assertEquals(content, helpContents.get(0).getContent());
+ assertEquals(roles, helpContents.get(0).getRoles());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ void assertNoPersonSectionHelp(QInstance qInstance)
+ {
+ List helpContents = qInstance.getTable(TestUtils.TABLE_NAME_PERSON).getSections()
+ .stream().filter(s -> s.getName().equals("identity")).findFirst()
+ .get().getHelpContents();
+ assertThat(helpContents).isNullOrEmpty();
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ void assertOnePersonSectionHelp(QInstance qInstance, String content, Set roles)
+ {
+ List helpContents = qInstance.getTable(TestUtils.TABLE_NAME_PERSON).getSections()
+ .stream().filter(s -> s.getName().equals("identity")).findFirst()
+ .get().getHelpContents();
+ assertEquals(1, helpContents.size());
+ assertEquals(content, helpContents.get(0).getContent());
+ assertEquals(roles, helpContents.get(0).getRoles());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ void assertNoGreetPersonFieldHelp(QInstance qInstance)
+ {
+ List helpContents = qInstance.getProcess(TestUtils.PROCESS_NAME_GREET_PEOPLE).getInputFields()
+ .stream().filter(f -> f.getName().equals("greetingPrefix")).findFirst()
+ .get().getHelpContents();
+ assertThat(helpContents).isNullOrEmpty();
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ void assertOneGreetPersonFieldHelp(QInstance qInstance, String content, Set roles)
+ {
+ List helpContents = qInstance.getProcess(TestUtils.PROCESS_NAME_GREET_PEOPLE).getInputFields()
+ .stream().filter(f -> f.getName().equals("greetingPrefix")).findFirst()
+ .get().getHelpContents();
+ assertEquals(1, helpContents.size());
+ assertEquals(content, helpContents.get(0).getContent());
+ assertEquals(roles, helpContents.get(0).getRoles());
+ }
+
+}
\ No newline at end of file