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