diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/AllowAllMetaDataFilter.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/AllowAllMetaDataFilter.java index c64c8954..de94474e 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/AllowAllMetaDataFilter.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/AllowAllMetaDataFilter.java @@ -33,6 +33,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; /******************************************************************************* ** a default implementation of MetaDataFilterInterface, that allows all the things *******************************************************************************/ +@Deprecated(since = "migrated to metaDataCustomizer") public class AllowAllMetaDataFilter implements MetaDataFilterInterface { diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/DefaultNoopMetaDataActionCustomizer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/DefaultNoopMetaDataActionCustomizer.java new file mode 100644 index 00000000..f7d88faf --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/DefaultNoopMetaDataActionCustomizer.java @@ -0,0 +1,92 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.actions.metadata; + + +import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput; +import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface; +import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; + + +/******************************************************************************* + ** a default implementation of MetaDataFilterInterface, that is all noop. + *******************************************************************************/ +public class DefaultNoopMetaDataActionCustomizer implements MetaDataActionCustomizerInterface +{ + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public boolean allowTable(MetaDataInput input, QTableMetaData table) + { + return (true); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public boolean allowProcess(MetaDataInput input, QProcessMetaData process) + { + return (true); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public boolean allowReport(MetaDataInput input, QReportMetaData report) + { + return (true); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public boolean allowApp(MetaDataInput input, QAppMetaData app) + { + return (true); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public boolean allowWidget(MetaDataInput input, QWidgetMetaDataInterface widget) + { + return (true); + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataAction.java index 56c76928..8ecf4dcf 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataAction.java @@ -23,10 +23,12 @@ package com.kingsrook.qqq.backend.core.actions.metadata; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import com.kingsrook.qqq.backend.core.actions.ActionHelper; import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader; import com.kingsrook.qqq.backend.core.actions.permissions.PermissionCheckResult; @@ -34,6 +36,7 @@ import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper; import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException; +import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException; import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput; import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput; @@ -65,7 +68,7 @@ public class MetaDataAction { private static final QLogger LOG = QLogger.getLogger(MetaDataAction.class); - private static Memoization metaDataFilterMemoization = new Memoization<>(); + private static Memoization metaDataActionCustomizerMemoization = new Memoization<>(); @@ -79,7 +82,7 @@ public class MetaDataAction MetaDataOutput metaDataOutput = new MetaDataOutput(); Map treeNodes = new LinkedHashMap<>(); - MetaDataFilterInterface filter = getMetaDataFilter(); + MetaDataActionCustomizerInterface customizer = getMetaDataActionCustomizer(); ///////////////////////////////////// // map tables to frontend metadata // @@ -90,7 +93,7 @@ public class MetaDataAction String tableName = entry.getKey(); QTableMetaData table = entry.getValue(); - if(!filter.allowTable(metaDataInput, table)) + if(!customizer.allowTable(metaDataInput, table)) { continue; } @@ -119,7 +122,7 @@ public class MetaDataAction String processName = entry.getKey(); QProcessMetaData process = entry.getValue(); - if(!filter.allowProcess(metaDataInput, process)) + if(!customizer.allowProcess(metaDataInput, process)) { continue; } @@ -144,7 +147,7 @@ public class MetaDataAction String reportName = entry.getKey(); QReportMetaData report = entry.getValue(); - if(!filter.allowReport(metaDataInput, report)) + if(!customizer.allowReport(metaDataInput, report)) { continue; } @@ -169,7 +172,7 @@ public class MetaDataAction String widgetName = entry.getKey(); QWidgetMetaDataInterface widget = entry.getValue(); - if(!filter.allowWidget(metaDataInput, widget)) + if(!customizer.allowWidget(metaDataInput, widget)) { continue; } @@ -206,7 +209,7 @@ public class MetaDataAction continue; } - if(!filter.allowApp(metaDataInput, app)) + if(!customizer.allowApp(metaDataInput, app)) { continue; } @@ -292,11 +295,22 @@ public class MetaDataAction metaDataOutput.setBranding(QContext.getQInstance().getBranding()); } - metaDataOutput.setEnvironmentValues(QContext.getQInstance().getEnvironmentValues()); + metaDataOutput.setEnvironmentValues(Objects.requireNonNullElse(QContext.getQInstance().getEnvironmentValues(), Collections.emptyMap())); - metaDataOutput.setHelpContents(QContext.getQInstance().getHelpContent()); + metaDataOutput.setHelpContents(Objects.requireNonNullElse(QContext.getQInstance().getHelpContent(), Collections.emptyMap())); - // todo post-customization - can do whatever w/ the result if you want? + try + { + customizer.postProcess(metaDataOutput); + } + catch(QUserFacingException e) + { + LOG.debug("User-facing exception thrown in meta-data customizer post-processing", e); + } + catch(Exception e) + { + LOG.warn("Unexpected error thrown in meta-data customizer post-processing", e); + } return metaDataOutput; } @@ -306,26 +320,36 @@ public class MetaDataAction /*************************************************************************** ** ***************************************************************************/ - private MetaDataFilterInterface getMetaDataFilter() + private MetaDataActionCustomizerInterface getMetaDataActionCustomizer() { - return metaDataFilterMemoization.getResult(QContext.getQInstance(), i -> + return metaDataActionCustomizerMemoization.getResult(QContext.getQInstance(), i -> { - MetaDataFilterInterface filter = null; - QCodeReference metaDataFilterReference = QContext.getQInstance().getMetaDataFilter(); - if(metaDataFilterReference != null) + MetaDataActionCustomizerInterface actionCustomizer = null; + QCodeReference metaDataActionCustomizerReference = QContext.getQInstance().getMetaDataActionCustomizer(); + if(metaDataActionCustomizerReference != null) { - filter = QCodeLoader.getAdHoc(MetaDataFilterInterface.class, metaDataFilterReference); - LOG.debug("Using new meta-data filter of type: " + filter.getClass().getSimpleName()); + actionCustomizer = QCodeLoader.getAdHoc(MetaDataActionCustomizerInterface.class, metaDataActionCustomizerReference); + LOG.debug("Using new meta-data actionCustomizer of type: " + actionCustomizer.getClass().getSimpleName()); } - if(filter == null) + if(actionCustomizer == null) { - filter = new AllowAllMetaDataFilter(); - LOG.debug("Using new default (allow-all) meta-data filter"); + QCodeReference metaDataFilterReference = QContext.getQInstance().getMetaDataFilter(); + if(metaDataFilterReference != null) + { + actionCustomizer = QCodeLoader.getAdHoc(MetaDataActionCustomizerInterface.class, metaDataFilterReference); + LOG.debug("Using new meta-data actionCustomizer (via metaDataFilter reference) of type: " + actionCustomizer.getClass().getSimpleName()); + } } - return (filter); - }).orElseThrow(() -> new QRuntimeException("Error getting metaDataFilter")); + if(actionCustomizer == null) + { + actionCustomizer = new DefaultNoopMetaDataActionCustomizer(); + LOG.debug("Using new default (allow-all) meta-data actionCustomizer"); + } + + return (actionCustomizer); + }).orElseThrow(() -> new QRuntimeException("Error getting MetaDataActionCustomizer")); } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataActionCustomizerInterface.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataActionCustomizerInterface.java new file mode 100644 index 00000000..fb2c108b --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataActionCustomizerInterface.java @@ -0,0 +1,78 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.actions.metadata; + + +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput; +import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput; +import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface; +import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; + + +/******************************************************************************* + ** Interface for customizations that can be injected by an application into + ** the MetaDataAction - e.g., loading applicable meta-data for a user into a + ** frontend. + *******************************************************************************/ +public interface MetaDataActionCustomizerInterface +{ + + /*************************************************************************** + ** + ***************************************************************************/ + boolean allowTable(MetaDataInput input, QTableMetaData table); + + /*************************************************************************** + ** + ***************************************************************************/ + boolean allowProcess(MetaDataInput input, QProcessMetaData process); + + /*************************************************************************** + ** + ***************************************************************************/ + boolean allowReport(MetaDataInput input, QReportMetaData report); + + /*************************************************************************** + ** + ***************************************************************************/ + boolean allowApp(MetaDataInput input, QAppMetaData app); + + /*************************************************************************** + ** + ***************************************************************************/ + boolean allowWidget(MetaDataInput input, QWidgetMetaDataInterface widget); + + /*************************************************************************** + ** + ***************************************************************************/ + default void postProcess(MetaDataOutput metaDataOutput) throws QException + { + ///////////////////// + // noop by default // + ///////////////////// + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataFilterInterface.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataFilterInterface.java index a7abb74d..a543764d 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataFilterInterface.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataFilterInterface.java @@ -22,43 +22,11 @@ package com.kingsrook.qqq.backend.core.actions.metadata; -import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput; -import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface; -import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; -import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; -import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData; -import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; - - /******************************************************************************* ** *******************************************************************************/ -public interface MetaDataFilterInterface +@Deprecated(since = "migrated to metaDataCustomizer") +public interface MetaDataFilterInterface extends MetaDataActionCustomizerInterface { - /*************************************************************************** - ** - ***************************************************************************/ - boolean allowTable(MetaDataInput input, QTableMetaData table); - - /*************************************************************************** - ** - ***************************************************************************/ - boolean allowProcess(MetaDataInput input, QProcessMetaData process); - - /*************************************************************************** - ** - ***************************************************************************/ - boolean allowReport(MetaDataInput input, QReportMetaData report); - - /*************************************************************************** - ** - ***************************************************************************/ - boolean allowApp(MetaDataInput input, QAppMetaData app); - - /*************************************************************************** - ** - ***************************************************************************/ - boolean allowWidget(MetaDataInput input, QWidgetMetaDataInterface widget); - } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java index b123858e..c73f9b8e 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java @@ -45,6 +45,7 @@ import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers; import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer; import com.kingsrook.qqq.backend.core.actions.metadata.JoinGraph; +import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataActionCustomizerInterface; import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataFilterInterface; import com.kingsrook.qqq.backend.core.actions.processes.BackendStep; import com.kingsrook.qqq.backend.core.actions.reporting.customizers.ReportCustomRecordSourceInterface; @@ -245,6 +246,11 @@ public class QInstanceValidator { validateSimpleCodeReference("Instance metaDataFilter ", qInstance.getMetaDataFilter(), MetaDataFilterInterface.class); } + + if(qInstance.getMetaDataActionCustomizer() != null) + { + validateSimpleCodeReference("Instance metaDataActionCustomizer ", qInstance.getMetaDataActionCustomizer(), MetaDataActionCustomizerInterface.class); + } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QInstance.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QInstance.java index def809b9..8e4ce83f 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QInstance.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QInstance.java @@ -116,8 +116,11 @@ public class QInstance private QPermissionRules defaultPermissionRules = QPermissionRules.defaultInstance(); private QAuditRules defaultAuditRules = QAuditRules.defaultInstanceLevelNone(); + @Deprecated(since = "migrated to metaDataCustomizer") private QCodeReference metaDataFilter = null; + private QCodeReference metaDataActionCustomizer = null; + ////////////////////////////////////////////////////////////////////////////////////// // todo - lock down the object (no more changes allowed) after it's been validated? // // if doing so, may need to copy all of the collections into read-only versions... // @@ -1495,6 +1498,7 @@ public class QInstance /******************************************************************************* ** Getter for metaDataFilter *******************************************************************************/ + @Deprecated(since = "migrated to metaDataCustomizer") public QCodeReference getMetaDataFilter() { return (this.metaDataFilter); @@ -1505,6 +1509,7 @@ public class QInstance /******************************************************************************* ** Setter for metaDataFilter *******************************************************************************/ + @Deprecated(since = "migrated to metaDataCustomizer") public void setMetaDataFilter(QCodeReference metaDataFilter) { this.metaDataFilter = metaDataFilter; @@ -1515,6 +1520,7 @@ public class QInstance /******************************************************************************* ** Fluent setter for metaDataFilter *******************************************************************************/ + @Deprecated(since = "migrated to metaDataCustomizer") public QInstance withMetaDataFilter(QCodeReference metaDataFilter) { this.metaDataFilter = metaDataFilter; @@ -1586,4 +1592,35 @@ public class QInstance return (this); } + + + /******************************************************************************* + ** Getter for metaDataActionCustomizer + *******************************************************************************/ + public QCodeReference getMetaDataActionCustomizer() + { + return (this.metaDataActionCustomizer); + } + + + + /******************************************************************************* + ** Setter for metaDataActionCustomizer + *******************************************************************************/ + public void setMetaDataActionCustomizer(QCodeReference metaDataActionCustomizer) + { + this.metaDataActionCustomizer = metaDataActionCustomizer; + } + + + + /******************************************************************************* + ** Fluent setter for metaDataActionCustomizer + *******************************************************************************/ + public QInstance withMetaDataActionCustomizer(QCodeReference metaDataActionCustomizer) + { + this.metaDataActionCustomizer = metaDataActionCustomizer; + return (this); + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/branding/Banner.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/branding/Banner.java new file mode 100644 index 00000000..299955c6 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/branding/Banner.java @@ -0,0 +1,269 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2025. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.model.metadata.branding; + + +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; + + +/******************************************************************************* + ** element of BrandingMetaData - content to send to a frontend for showing a + ** user across the whole UI - e.g., what environment you're in, or a message + ** about your account - site announcements, etc. + *******************************************************************************/ +public class Banner implements Serializable, Cloneable +{ + private Severity severity; + private String textColor; + private String backgroundColor; + private String messageText; + private String messageHTML; + + private Map additionalStyles; + + + + /*************************************************************************** + ** + ***************************************************************************/ + public enum Severity + { + INFO, WARNING, ERROR, SUCCESS + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public Banner clone() + { + try + { + Banner clone = (Banner) super.clone(); + + ////////////////////////////////////////////////////////////////////////////////////// + // copy mutable state here, so the clone can't change the internals of the original // + ////////////////////////////////////////////////////////////////////////////////////// + if(additionalStyles != null) + { + clone.setAdditionalStyles(new LinkedHashMap<>(additionalStyles)); + } + + return clone; + } + catch(CloneNotSupportedException e) + { + throw new AssertionError(); + } + } + + + + /******************************************************************************* + ** Getter for textColor + *******************************************************************************/ + public String getTextColor() + { + return (this.textColor); + } + + + + /******************************************************************************* + ** Setter for textColor + *******************************************************************************/ + public void setTextColor(String textColor) + { + this.textColor = textColor; + } + + + + /******************************************************************************* + ** Fluent setter for textColor + *******************************************************************************/ + public Banner withTextColor(String textColor) + { + this.textColor = textColor; + return (this); + } + + + + /******************************************************************************* + ** Getter for backgroundColor + *******************************************************************************/ + public String getBackgroundColor() + { + return (this.backgroundColor); + } + + + + /******************************************************************************* + ** Setter for backgroundColor + *******************************************************************************/ + public void setBackgroundColor(String backgroundColor) + { + this.backgroundColor = backgroundColor; + } + + + + /******************************************************************************* + ** Fluent setter for backgroundColor + *******************************************************************************/ + public Banner withBackgroundColor(String backgroundColor) + { + this.backgroundColor = backgroundColor; + return (this); + } + + + + /******************************************************************************* + ** Getter for additionalStyles + *******************************************************************************/ + public Map getAdditionalStyles() + { + return (this.additionalStyles); + } + + + + /******************************************************************************* + ** Setter for additionalStyles + *******************************************************************************/ + public void setAdditionalStyles(Map additionalStyles) + { + this.additionalStyles = additionalStyles; + } + + + + /******************************************************************************* + ** Fluent setter for additionalStyles + *******************************************************************************/ + public Banner withAdditionalStyles(Map additionalStyles) + { + this.additionalStyles = additionalStyles; + return (this); + } + + + + /******************************************************************************* + ** Getter for messageText + *******************************************************************************/ + public String getMessageText() + { + return (this.messageText); + } + + + + /******************************************************************************* + ** Setter for messageText + *******************************************************************************/ + public void setMessageText(String messageText) + { + this.messageText = messageText; + } + + + + /******************************************************************************* + ** Fluent setter for messageText + *******************************************************************************/ + public Banner withMessageText(String messageText) + { + this.messageText = messageText; + return (this); + } + + + + /******************************************************************************* + ** Getter for messageHTML + *******************************************************************************/ + public String getMessageHTML() + { + return (this.messageHTML); + } + + + + /******************************************************************************* + ** Setter for messageHTML + *******************************************************************************/ + public void setMessageHTML(String messageHTML) + { + this.messageHTML = messageHTML; + } + + + + /******************************************************************************* + ** Fluent setter for messageHTML + *******************************************************************************/ + public Banner withMessageHTML(String messageHTML) + { + this.messageHTML = messageHTML; + return (this); + } + + + + /******************************************************************************* + ** Getter for severity + *******************************************************************************/ + public Severity getSeverity() + { + return (this.severity); + } + + + + /******************************************************************************* + ** Setter for severity + *******************************************************************************/ + public void setSeverity(Severity severity) + { + this.severity = severity; + } + + + + /******************************************************************************* + ** Fluent setter for severity + *******************************************************************************/ + public Banner withSeverity(Severity severity) + { + this.severity = severity; + return (this); + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/branding/BannerSlot.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/branding/BannerSlot.java new file mode 100644 index 00000000..162f6758 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/branding/BannerSlot.java @@ -0,0 +1,31 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2025. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.model.metadata.branding; + + +/******************************************************************************* + ** interface to define keys for where banners should be displayed. + ** expect frontends to implement this interface with enums of known possible values + *******************************************************************************/ +public interface BannerSlot +{ +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/branding/QBrandingMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/branding/QBrandingMetaData.java index 6eb01b58..d354a8e3 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/branding/QBrandingMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/branding/QBrandingMetaData.java @@ -22,6 +22,9 @@ package com.kingsrook.qqq.backend.core.model.metadata.branding; +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface; @@ -30,7 +33,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface; ** Meta-Data to define branding in a QQQ instance. ** *******************************************************************************/ -public class QBrandingMetaData implements TopLevelMetaDataInterface +public class QBrandingMetaData implements TopLevelMetaDataInterface, Cloneable, Serializable { private String companyName; private String companyUrl; @@ -39,9 +42,45 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface private String icon; private String accentColor; + @Deprecated(since = "migrate to use banners map instead") private String environmentBannerText; + + @Deprecated(since = "migrate to use banners map instead") private String environmentBannerColor; + private Map banners; + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public QBrandingMetaData clone() + { + try + { + QBrandingMetaData clone = (QBrandingMetaData) super.clone(); + + ////////////////////////////////////////////////////////////////////////////////////// + // copy mutable state here, so the clone can't change the internals of the original // + ////////////////////////////////////////////////////////////////////////////////////// + if(banners != null) + { + clone.banners = new LinkedHashMap<>(); + for(Map.Entry entry : this.banners.entrySet()) + { + clone.banners.put(entry.getKey(), entry.getValue().clone()); + } + } + + return clone; + } + catch(CloneNotSupportedException e) + { + throw new AssertionError(); + } + } + /******************************************************************************* @@ -267,6 +306,7 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface /******************************************************************************* ** Getter for environmentBannerText *******************************************************************************/ + @Deprecated(since = "migrate to use banners map instead") public String getEnvironmentBannerText() { return (this.environmentBannerText); @@ -277,6 +317,7 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface /******************************************************************************* ** Setter for environmentBannerText *******************************************************************************/ + @Deprecated(since = "migrate to use banners map instead") public void setEnvironmentBannerText(String environmentBannerText) { this.environmentBannerText = environmentBannerText; @@ -287,6 +328,7 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface /******************************************************************************* ** Fluent setter for environmentBannerText *******************************************************************************/ + @Deprecated(since = "migrate to use banners map instead") public QBrandingMetaData withEnvironmentBannerText(String environmentBannerText) { this.environmentBannerText = environmentBannerText; @@ -298,6 +340,7 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface /******************************************************************************* ** Getter for environmentBannerColor *******************************************************************************/ + @Deprecated(since = "migrate to use banners map instead") public String getEnvironmentBannerColor() { return (this.environmentBannerColor); @@ -308,6 +351,7 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface /******************************************************************************* ** Setter for environmentBannerColor *******************************************************************************/ + @Deprecated(since = "migrate to use banners map instead") public void setEnvironmentBannerColor(String environmentBannerColor) { this.environmentBannerColor = environmentBannerColor; @@ -318,6 +362,7 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface /******************************************************************************* ** Fluent setter for environmentBannerColor *******************************************************************************/ + @Deprecated(since = "migrate to use banners map instead") public QBrandingMetaData withEnvironmentBannerColor(String environmentBannerColor) { this.environmentBannerColor = environmentBannerColor; @@ -334,4 +379,52 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface { qInstance.setBranding(this); } + + + /******************************************************************************* + ** Getter for banners + *******************************************************************************/ + public Map getBanners() + { + return (this.banners); + } + + + + /******************************************************************************* + ** Setter for banners + *******************************************************************************/ + public void setBanners(Map banners) + { + this.banners = banners; + } + + + + /******************************************************************************* + ** Fluent setter for banners + *******************************************************************************/ + public QBrandingMetaData withBanners(Map banners) + { + this.banners = banners; + return (this); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + public QBrandingMetaData withBanner(BannerSlot slot, Banner banner) + { + if(this.banners == null) + { + this.banners = new LinkedHashMap<>(); + } + this.banners.put(slot, banner); + + return (this); + } + + } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataActionTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataActionTest.java index 58d85069..4d75312d 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataActionTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataActionTest.java @@ -364,6 +364,7 @@ class MetaDataActionTest extends BaseTest ** *******************************************************************************/ @Test + @Deprecated(since = "migrated to metaDataCustomizer") void testFilter() throws QException { QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(MetaDataAction.class); @@ -397,7 +398,7 @@ class MetaDataActionTest extends BaseTest // run again (with the same instance as before) to assert about memoization of the filter based on the QInstance // /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// new MetaDataAction().execute(new MetaDataInput()); - assertThat(collectingLogger.getCollectedMessages()).filteredOn(clm -> clm.getMessage().contains("filter of type: DenyAllFilter")).hasSize(1); + assertThat(collectingLogger.getCollectedMessages()).filteredOn(clm -> clm.getMessage().contains("actionCustomizer (via metaDataFilter reference) of type: DenyAllFilter")).hasSize(1); QLogger.deactivateCollectingLoggerForClass(MetaDataAction.class); @@ -413,6 +414,59 @@ class MetaDataActionTest extends BaseTest + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testCustomizer() throws QException + { + QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(MetaDataAction.class); + + ////////////////////////////////////////////////////// + // run default version, and assert tables are found // + ////////////////////////////////////////////////////// + MetaDataOutput result = new MetaDataAction().execute(new MetaDataInput()); + assertFalse(result.getTables().isEmpty(), "should be some tables"); + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // run again (with the same instance as before) to assert about memoization of the filter based on the QInstance // + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + new MetaDataAction().execute(new MetaDataInput()); + assertThat(collectingLogger.getCollectedMessages()).filteredOn(clm -> clm.getMessage().contains("Using new default")).hasSize(1); + + ///////////////////////////////////////////////////////////// + // set up new instance to use a custom filter, to deny all // + ///////////////////////////////////////////////////////////// + QInstance instance = TestUtils.defineInstance(); + instance.setMetaDataActionCustomizer(new QCodeReference(DenyAllFilteringCustomizer.class)); + reInitInstanceInContext(instance); + + ///////////////////////////////////////////////////// + // re-run, and assert all tables are filtered away // + ///////////////////////////////////////////////////// + result = new MetaDataAction().execute(new MetaDataInput()); + assertTrue(result.getTables().isEmpty(), "should be no tables"); + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // run again (with the same instance as before) to assert about memoization of the filter based on the QInstance // + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + new MetaDataAction().execute(new MetaDataInput()); + assertThat(collectingLogger.getCollectedMessages()).filteredOn(clm -> clm.getMessage().contains("meta-data actionCustomizer of type: DenyAllFilteringCustomizer")).hasSize(1); + + QLogger.deactivateCollectingLoggerForClass(MetaDataAction.class); + + ///////////////////////////////////////////////////////////////////////////////// + // run now with the DefaultNoopMetaDataActionCustomizer, confirm we get tables // + ///////////////////////////////////////////////////////////////////////////////// + instance = TestUtils.defineInstance(); + instance.setMetaDataActionCustomizer(new QCodeReference(DefaultNoopMetaDataActionCustomizer.class)); + reInitInstanceInContext(instance); + result = new MetaDataAction().execute(new MetaDataInput()); + assertFalse(result.getTables().isEmpty(), "should be some tables"); + } + + + /*************************************************************************** ** ***************************************************************************/ @@ -462,6 +516,67 @@ class MetaDataActionTest extends BaseTest + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public boolean allowWidget(MetaDataInput input, QWidgetMetaDataInterface widget) + { + return false; + } + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + public static class DenyAllFilteringCustomizer implements MetaDataActionCustomizerInterface + { + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public boolean allowTable(MetaDataInput input, QTableMetaData table) + { + return false; + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public boolean allowProcess(MetaDataInput input, QProcessMetaData process) + { + return false; + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public boolean allowReport(MetaDataInput input, QReportMetaData report) + { + return false; + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public boolean allowApp(MetaDataInput input, QAppMetaData app) + { + return false; + } + + + /*************************************************************************** ** ***************************************************************************/ diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java index 8419b05a..d790ca59 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java @@ -40,6 +40,7 @@ import com.kingsrook.qqq.backend.core.actions.dashboard.PersonsByCreateDateBarCh import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer; import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.ParentWidgetRenderer; import com.kingsrook.qqq.backend.core.actions.metadata.AllowAllMetaDataFilter; +import com.kingsrook.qqq.backend.core.actions.metadata.DefaultNoopMetaDataActionCustomizer; import com.kingsrook.qqq.backend.core.actions.processes.CancelProcessActionTest; import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe; import com.kingsrook.qqq.backend.core.actions.reporting.customizers.ReportCustomRecordSourceInterface; @@ -160,6 +161,20 @@ public class QInstanceValidatorTest extends BaseTest } + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testMetaDataActionCustomizer() + { + assertValidationFailureReasons((qInstance) -> qInstance.setMetaDataActionCustomizer(new QCodeReference(QInstanceValidator.class)), + "Instance metaDataActionCustomizer CodeReference is not of the expected type"); + + assertValidationSuccess((qInstance) -> qInstance.setMetaDataActionCustomizer(new QCodeReference(DefaultNoopMetaDataActionCustomizer.class))); + assertValidationSuccess((qInstance) -> qInstance.setMetaDataActionCustomizer(null)); + } + + /******************************************************************************* ** Test an instance with null backends - should throw.