diff --git a/nebula-logger-plugins/Log-Retention-Rules/README.md b/nebula-logger-plugins/Log-Retention-Rules/README.md new file mode 100644 index 000000000..72e6f78a3 --- /dev/null +++ b/nebula-logger-plugins/Log-Retention-Rules/README.md @@ -0,0 +1,43 @@ +# Log Retention Rules plugin for Nebula Logger + +> :information_source: Requires Nebula Logger v4.6.12 or newer + +Adds the ability to create & deploy advanced, configurable rules for setting the retention date of `Log__c` records, using custom metadata types `LogRetentionRule__mdt` and `LogRetentionRuleCondition__mdt`. + +[![Install Unlocked Package Plugin](./content/btn-install-unlocked-package-plugin.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=zzzzzzzz) +[![Install Unlocked Package Plugin](./content/btn-install-unlocked-package-plugin-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=zzzzzzzz) + +--- + +## What's Included + +This plugin includes some add-on metadata for Logger to support the Slack integration + +1. Plugin configuration details stored in Logger's CMDT objects `LoggerPlugin__mdt.LogRetentionRules` +2. New custom metadata types + - `LogRetentionRule__mdt` - Used to configure rules that set the value of `Log__c`.`LogRetentionDate__c`. Each rules consists of 1 or more conditions, stored in `LogRetentionRuleCondition__mdt`. + - `LogRetentionRuleCondition__mdt` - Used to configure field-level conditions for retention rules - each condition checks a `LogEntry__c` or `Log__c` field for a specific value, regular expression (regex), or field comparisons. +3. Apex class `LogRetentionRulesPlugin` (and corresponding tests in `LogRetentionRulesPlugin_Tests`). The plugin class reads the configured rules & conditions and automatically sets the field `Log__c.LogRetentionDate__c` when new `Log__c` records are inserted. + +--- + +## Installation Steps + +After installing the unlocked package Nebula Logger v4.6.12 (or newer), you can then install the Log Retention Rules plugin unlocked package in a sandbox. + +This plugin is currently in beta and cannot be installed in production - however, if you want to use it in your production org, you can deploy the metadata to your org using your preferred deployment tool (changes sets, sfdx, the metadata API, etc.). + +### Quick Start: Adding Log Retention Rules and Conditions + +After installing the plugin, you can add rules using the custom metadata types `LogRetentionRule__mdt` and `LogRetentionRuleCondition__mdt`. Multiple rules can be configured in your orgs, as shown below: + +![Log Retention Rules plugin: Example Rule](./content/example-log-retention-rules-list-view.png) + +In this example rule, for every inserted `LogEntry__c` record, 2 conditions are checked: + +1. Does the entry's parent `Log__c` record have 1 or more error? (based on the roll-up summary field `Log__c.TotalERRORLogEntries__c`) +2. Does the entry's parent `Log__c` record have the scenario 'feature B'? (based on the text field `Log__c.Scenario__c`) + +If any `LogEntry__c` record is inserted that meets these 2 conditions, then the parent `Log__c` record will have the field `LogRetentionDate__c` set to `System.today() + 30`. + +![Log Retention Rules plugin: Example Rule](./content/example-log-retention-rule-with-conditions.png) diff --git a/nebula-logger-plugins/Log-Retention-Rules/content/btn-install-unlocked-package-plugin.png b/nebula-logger-plugins/Log-Retention-Rules/content/btn-install-unlocked-package-plugin.png new file mode 100644 index 000000000..9c3bbf6a8 Binary files /dev/null and b/nebula-logger-plugins/Log-Retention-Rules/content/btn-install-unlocked-package-plugin.png differ diff --git a/nebula-logger-plugins/Log-Retention-Rules/content/example-log-retention-rule-with-conditions.png b/nebula-logger-plugins/Log-Retention-Rules/content/example-log-retention-rule-with-conditions.png new file mode 100644 index 000000000..112a4e100 Binary files /dev/null and b/nebula-logger-plugins/Log-Retention-Rules/content/example-log-retention-rule-with-conditions.png differ diff --git a/nebula-logger-plugins/Log-Retention-Rules/content/example-log-retention-rules-list-view.png b/nebula-logger-plugins/Log-Retention-Rules/content/example-log-retention-rules-list-view.png new file mode 100644 index 000000000..78835b50c Binary files /dev/null and b/nebula-logger-plugins/Log-Retention-Rules/content/example-log-retention-rules-list-view.png differ diff --git a/nebula-logger-plugins/Log-Retention-Rules/plugin/classes/LogRetentionRulesPlugin.cls b/nebula-logger-plugins/Log-Retention-Rules/plugin/classes/LogRetentionRulesPlugin.cls new file mode 100644 index 000000000..43f9bc79b --- /dev/null +++ b/nebula-logger-plugins/Log-Retention-Rules/plugin/classes/LogRetentionRulesPlugin.cls @@ -0,0 +1,523 @@ +//-----------------------------------------------------------------------------------------------------------// +// This file is part of the Nebula Logger project, released under the MIT License. // +// The core functionality of this plugin's code originated in https://github.com/jongpie/ApexValidationRules // +// See LICENSE file or go to https://github.com/jongpie/NebulaLogger for full license details. // +//-----------------------------------------------------------------------------------------------------------// + +// TODO revise suppressed PMD rules/clean up code +@SuppressWarnings('PMD.ApexDoc, PMD.AvoidDebugStatements, PMD.DebugsShouldUseLoggingLevel, PMD.ExcessiveParameterList') +public without sharing class LogRetentionRulesPlugin extends LoggerSObjectHandlerPlugin { + private static final Map RULE_NAME_TO_RULE = new Map(); + private static final Map> RULE_NAME_TO_CONDITIONS = new Map>(); + + public LogRetentionRulesPlugin() { + this.loadConfiguredFilters(); + } + + public override void execute( + TriggerOperation triggerOperationType, + List triggerNew, + Map triggerNewMap, + List triggerOld, + Map triggerOldMap + ) { + switch on triggerOperationType { + when AFTER_INSERT { + List logEntries = requeryLogEntries((List) triggerNew); + this.setLogRetentionDate(logEntries); + } + } + } + + private List requeryLogEntries(List logEntries) { + // Requery the LogEntry__c records so the parent Log__c fields can be included/used in rules + List logEntryFieldNames = new List(Schema.LogEntry__c.SObjectType.getDescribe().fields.getMap().keySet()); + + List logFieldNames = new List(Schema.Log__c.SObjectType.getDescribe().fields.getMap().keySet()); + logFieldNames.addAll(new List{ 'Owner.Name', 'Owner.Type' }); + for (String logFieldName : logFieldNames) { + logEntryFieldNames.add('Log__r.' + logFieldName); + } + + List textReplacements = new List{ String.join(logEntryFieldNames, ',') }; + String logEntryQuery = String.format('SELECT {0} FROM LogEntry__c WHERE Id IN :logEntries', textReplacements); + return (List) Database.query(logEntryQuery); + } + + private void setLogRetentionDate(List logEntries) { + System.debug('starting setLogRetentionDate==' + logEntries); + + Map logIdToLog = new Map(); + List filterResults = this.runFilters(logEntries); + System.debug('filterResults==' + filterResults); + for (FilterResult filterResult : filterResults) { + System.debug('filterResult==' + filterResult); + System.debug('filter matches for ' + filterResult.rule.DeveloperName + '==' + filterResult.matchesFilter); + if (filterResult.matchesFilter == true) { + Id logId = (Id) filterResult.record.get(Schema.LogEntry__c.Log__c); + Log__c log = logIdToLog.get(logId); + if (log == null) { + log = new Log__c(Id = logId); + } + + Integer numberOfDaysToRetainLogs = Integer.valueOf(filterResult.rule.NumberOfDaysToRetainLogs__c); + log.LogRetentionDate__c = numberOfDaysToRetainLogs == null ? null : System.today().addDays(numberOfDaysToRetainLogs); + + logIdToLog.put(log.Id, log); + } + } + System.debug('logIdToLog==' + logIdToLog.values()); + update logIdToLog.values(); + } + + // Private methods + private void loadConfiguredFilters() { + Map queriedRulesByDeveloperName = new Map(); + Map> queriedConditionsByRuleDeveloperName = new Map>(); + for (LogRetentionRule__mdt rule : [ + SELECT + DeveloperName, + ConditionLogicType__c, + CustomConditionLogic__c, + NumberOfDaysToRetainLogs__c, + (SELECT FieldPath__c, Operator__c, ValueType__c, Value__c FROM LogRetentionRuleConditions__r ORDER BY SortOrder__c NULLS LAST, DeveloperName) + FROM LogRetentionRule__mdt + WHERE IsEnabled__c = TRUE + ORDER BY ExecutionOrder__c NULLS LAST, DeveloperName + ]) { + queriedRulesByDeveloperName.put(rule.DeveloperName, rule); + queriedConditionsByRuleDeveloperName.put(rule.DeveloperName, rule.LogRetentionRuleConditions__r); + + if (Test.isRunningTest() == true) { + queriedRulesByDeveloperName.clear(); + queriedConditionsByRuleDeveloperName.clear(); + } + RULE_NAME_TO_RULE.putAll(queriedRulesByDeveloperName); + RULE_NAME_TO_CONDITIONS.putAll(queriedConditionsByRuleDeveloperName); + } + } + + private List runFilters(List records) { + System.debug('runFilters for records: ' + records); + System.debug('runFilters for RULE_NAME_TO_RULE: ' + RULE_NAME_TO_RULE); + List results = new List(); + for (SObject record : records) { + for (String filterDeveloperName : RULE_NAME_TO_RULE.keySet()) { + System.debug('processing filter: ' + filterDeveloperName); + LogRetentionRule__mdt filter = RULE_NAME_TO_RULE.get(filterDeveloperName); + List filerConditions = RULE_NAME_TO_CONDITIONS.get(filter.DeveloperName); + FilterResult filterResult = new FilterResult(record, filter, filerConditions); + + results.add(filterResult); + } + } + return results; + } + + @TestVisible + private static void setMockRetentionRule(LogRetentionRule__mdt rule) { + RULE_NAME_TO_RULE.put(rule.DeveloperName, rule); + } + + @TestVisible + private static void setMockRetentionRuleConditions(LogRetentionRule__mdt rule, List conditions) { + RULE_NAME_TO_CONDITIONS.put(rule.DeveloperName, conditions); + } + + @SuppressWarnings('PMD.ApexDoc, PMD.CyclomaticComplexity, PMD.FieldDeclarationsShouldBeAtStart') + @TestVisible + private class FilterResult { + public SObject record { get; private set; } + public List conditions { get; private set; } + public String conditionsLogic { get; private set; } + public String conditionsLogicType { get; private set; } + public LogRetentionRule__mdt rule { get; private set; } + public Boolean matchesFilter { get; private set; } + + private List filerConditions; + + public FilterResult(SObject record, LogRetentionRule__mdt rule, List filerConditions) { + this.record = record; + this.rule = rule; + this.filerConditions = filerConditions; + + this.conditions = new List(); + this.conditionsLogic = this.getFilterConditionsLogic(); + this.conditionsLogicType = rule.ConditionLogicType__c; + + this.process(); + } + + private void process() { + List booleanValues = new List(); + for (LogRetentionRuleCondition__mdt filerCondition : this.filerConditions) { + FilterConditionResult filerConditionResult = new FilterConditionResult(this.record, filerCondition); + this.conditions.add(filerConditionResult.getCondition()); + booleanValues.add(String.valueOf(filerConditionResult.matchesFilter)); + } + + String parsedConditionsLogic = String.format(this.getFilterConditionsLogic(), booleanValues); + + this.matchesFilter = new BooleanExpression().evaluate(parsedConditionsLogic); + } + + private String getFilterConditionsLogic() { + String conditionsLogic = String.isBlank(this.rule.CustomConditionLogic__c) ? '' : this.rule.CustomConditionLogic__c; + + if (this.rule.ConditionLogicType__c != 'Custom') { + List standardLogicPieces = new List(); + for (Integer i = 0; i < this.filerConditions.size(); i++) { + standardLogicPieces.add(String.valueOf(i + 1)); + } + conditionsLogic = '(' + String.join(standardLogicPieces, ' ' + this.rule.ConditionLogicType__c + ' ') + ')'; + } + + List parsedCharacters = new List(); + Boolean hasFoundNumbers = false; + String foundNumberString = ''; + + for (String character : conditionsLogic.split('')) { + if (!character.isNumeric() && !hasFoundNumbers) { + parsedCharacters.add(character); + } else if (!character.isNumeric() && hasFoundNumbers) { + hasFoundNumbers = false; + Integer foundNumber = Integer.valueOf(foundNumberString) - 1; + + parsedCharacters.add('{' + foundNumber + '}'); + foundNumberString = ''; + parsedCharacters.add(character); + } else if (character.isNumeric()) { + hasFoundNumbers = true; + foundNumberString += character; + } else if (hasFoundNumbers && !character.isNumeric() && !String.isBlank(foundNumberString)) { + Integer foundNumber = Integer.valueOf(foundNumberString) - 1; + + parsedCharacters.add('{' + foundNumber + '}'); + foundNumberString = ''; + } else { + parsedCharacters.add(character); + } + } + return String.join(parsedCharacters, '').toUpperCase(); + } + } + + @SuppressWarnings('PMD.ApexDoc') + @TestVisible + private class FilterConditionResult { + private Boolean matchesFilter; + private SObject record; + private LogRetentionRuleCondition__mdt filerCondition; + private Schema.SObjectType sobjectType; + + private FilterConditionResult(SObject record, LogRetentionRuleCondition__mdt filerCondition) { + this.sobjectType = record.getSObjectType(); + this.record = record; + this.filerCondition = filerCondition; + + this.matchesFilter = this.matchesFilter(); + } + + public String getCondition() { + return this.filerCondition.FieldPath__c + + ' ' + + this.filerCondition.Operator__c + + ' ' + + this.getComparisonValue() + + ' (' + + this.filerCondition.ValueType__c + + ')'; + } + + public Boolean matchesFilter() { + if (this.filerCondition.ValueType__c == 'RegEx') { + return this.matchesRegEx(); + } + + Schema.SObjectField field = new FieldPath(this.sobjectType, this.filerCondition.FieldPath__c).getField(); + + Object recordFieldValue = this.getFieldValue(); + Object comparisonValue = this.getComparisonValue(); + + switch on field.getDescribe().getSoapType() { + when DOUBLE, INTEGER { + return this.compareDecimal((Decimal) recordFieldValue, this.getAsDecimal(comparisonValue)); + } + when DATETIME { + return this.compareDatetime((Datetime) recordFieldValue, this.getAsDatetime(comparisonValue)); + } + when STRING, ID { + return this.compareString((String) recordFieldValue, String.valueOf(comparisonValue)); + } + when else { + throw new IllegalArgumentException('Could not process field path: ' + this.filerCondition.FieldPath__c); + } + } + } + + private Boolean matchesRegEx() { + Pattern pattern = Pattern.compile(this.filerCondition.Value__c); + return pattern.matcher(String.valueOf(this.getFieldValue())).matches(); + } + + private Object getFieldValue() { + return new FieldPath(this.sobjectType, this.filerCondition.FieldPath__c).getValue(this.record); + } + + private Object getComparisonValue() { + switch on this.filerCondition.ValueType__c { + when 'Field' { + return new FieldPath(this.sobjectType, this.filerCondition.Value__c).getValue(this.record); + } + when 'RegEx' { + return this.filerCondition.Value__c; + } + when 'Value' { + return this.filerCondition.Value__c; + } + when else { + throw new IllegalArgumentException('Unknown Value Type, cannot parse comparison value'); + } + } + } + + // Helper methods for dealing with converting field values & strings + // (stored in CMDT) to the appropriate data type + private Datetime getAsDatetime(Object datetimeValue) { + if (datetimeValue == null) { + return null; + } else if (datetimeValue instanceof Datetime) { + return (Datetime) datetimeValue; + } else { + String datetimeString = (String) datetimeValue; + return (Datetime) JSON.deserialize(datetimeString, Datetime.class); + } + } + + private Decimal getAsDecimal(Object decimalValue) { + if (decimalValue == null) { + return null; + } else if (decimalValue instanceof Decimal) { + return (Decimal) decimalValue; + } else { + String decimalString = (String) decimalValue; + return (Decimal) JSON.deserialize(decimalString, Decimal.class); + } + } + + // In Apex, you can't use comparison operators on instances of Object, so several private methods are used for each data type + // Example of what you can't do in Apex: + // Object today = System.today(); + // Object yesterday = System.today().addDays(-1); + // System.assert(today > yesterday); // This line cannot execute since it's comparing Object + private Boolean compareDatetime(Datetime recordFieldValue, Datetime comparisonValue) { + switch on this.filerCondition.Operator__c { + when 'EQUAL_TO' { + return recordFieldValue == comparisonValue; + } + when 'NOT_EQUAL_TO' { + return recordFieldValue != comparisonValue; + } + when 'LESS_THAN' { + return recordFieldValue < comparisonValue; + } + when 'LESS_THAN_OR_EQUAL_TO' { + return recordFieldValue <= comparisonValue; + } + when 'GREATER_THAN' { + return recordFieldValue > comparisonValue; + } + when 'GREATER_THAN_OR_EQUAL_TO' { + return recordFieldValue >= comparisonValue; + } + when else { + throw new IllegalArgumentException('Unsupported operator for Datetime: ' + this.filerCondition.Operator__c); + } + } + } + + private Boolean compareDecimal(Decimal recordFieldValue, Decimal comparisonValue) { + switch on this.filerCondition.Operator__c { + when 'EQUAL_TO' { + return recordFieldValue == comparisonValue; + } + when 'NOT_EQUAL_TO' { + return recordFieldValue != comparisonValue; + } + when 'LESS_THAN' { + return recordFieldValue < comparisonValue; + } + when 'LESS_THAN_OR_EQUAL_TO' { + return recordFieldValue <= comparisonValue; + } + when 'GREATER_THAN' { + return recordFieldValue > comparisonValue; + } + when 'GREATER_THAN_OR_EQUAL_TO' { + return recordFieldValue >= comparisonValue; + } + when else { + throw new IllegalArgumentException('Unsupported operator for Decimal: ' + this.filerCondition.Operator__c); + } + } + } + + private Boolean compareString(String recordFieldValue, String comparisonValue) { + switch on this.filerCondition.Operator__c { + when 'EQUAL_TO' { + return recordFieldValue == comparisonValue; + } + when 'NOT_EQUAL_TO' { + return recordFieldValue != comparisonValue; + } + when 'STARTS_WITH' { + return recordFieldValue.startsWith(comparisonValue); + } + when 'CONTAINS' { + return recordFieldValue.contains(comparisonValue); + } + when 'ENDS_WITH' { + return recordFieldValue.endsWith(comparisonValue); + } + when else { + throw new IllegalArgumentException('Unsupported operator for String: ' + this.filerCondition.Operator__c); + } + } + } + } + + // Credit goes to this StackExchange post for the original BooleanExpression class - + // below is a modified version of the class + // https://salesforce.stackexchange.com/questions/113300/boolean-evaluation-in-apex/113308 + @SuppressWarnings('PMD.ApexDoc') + private class BooleanExpression { + public Boolean evaluate(String x) { + x = simplify(x); + + if (isSimpleExpression(x)) { + return Boolean.valueOf(x); + } + + if (x.contains('&&')) { + return andJoin(x.split('&&', 2)[0], x.split('&&', 2)[1]); + } + + if (x.contains('||')) { + String p1 = x.split('\\|\\|', 2)[0]; + String p2 = x.split('\\|\\|', 2)[1]; + + return orJoin(p1, p2); + } + + if (x.startsWith('!')) { + return !evaluate(x.substring(1)); + } + + return Boolean.valueOf(x); + } + + private Boolean orJoin(String x, String y) { + return evaluate(x) || evaluate(y); + } + + private Boolean andJoin(String x, String y) { + return evaluate(x) && evaluate(y); + } + + private Boolean isSimpleExpression(String x) { + return x == 'true' || x == 'false'; + } + + private String simplify(String x) { + x = x.trim(); + x = x.replace('AND', '&&'); + x = x.replace('OR', '||'); + while (x.contains('(') == true) { + String sub = x.substringAfterLast('(').substringBefore(')'); + x = x.replace('(' + sub + ')', String.valueOf(evaluate(sub))); + } + return x; + } + } + + @SuppressWarnings('PMD.ApexDoc') + private class FieldPath { + private List fieldChain; + private Schema.DescribeFieldResult fieldDescribe; + private String fieldPath; + private Schema.SObjectType sobjectType; + + public FieldPath(Schema.SObjectType sobjectType, String fieldPath) { + this.fieldChain = this.getFieldChain(sobjectType, fieldPath); + this.fieldPath = fieldPath; + + this.fieldDescribe = this.getLastFieldDescribe(); + } + + public Schema.SObjectField getField() { + return this.fieldChain[this.fieldChain.size() - 1]; + } + + public Object getValue(SObject record) { + Schema.SObjectType parentSObjectType = this.sobjectType; + SObject parentRecord = record; + + for (Schema.SObjectField field : this.fieldChain) { + Schema.DescribeFieldResult fieldDescribe = field.getDescribe(); + // TODO delete? String relationshipName = fieldDescribe.getRelationshipName(); + + if (fieldDescribe.getSoapType() != Schema.SoapType.Id) { + return parentRecord.get(fieldDescribe.getName()); + } else { + parentSObjectType = fieldDescribe.getReferenceTo().get(0); + + SObject newParentRecord = parentRecord.getSObject(field); + if (newParentRecord == null) { + return null; + } else { + parentRecord = newParentRecord; + } + } + } + + return null; + } + + private List getFieldChain(Schema.SObjectType sobjectType, String fieldPath) { + Schema.SObjectType currentSObjectType = sobjectType; + + List fields = new List(); + List fieldPathPieces = fieldPath.split('\\.'); + Integer lastFieldIndex = fieldPathPieces.size() <= 1 ? 0 : fieldPathPieces.size() - 1; + + for (Integer i = 0; i < fieldPathPieces.size(); i++) { + String fieldPathPiece = fieldPathPieces[i]; + + String fieldApiName; + if (i == lastFieldIndex) { + fieldApiName = fieldPathPiece; + } else if (fieldPathPiece.endsWith('__r')) { + fieldApiName = fieldPathPiece.replace('__r', '__c'); + } else { + fieldApiName = fieldPathPiece + 'Id'; + } + + Schema.SObjectField field = currentSObjectType.getDescribe().fields.getMap().get(fieldApiName); + + // TODO add support for polymorphic fields + if (i < lastFieldIndex) { + currentSObjectType = field.getDescribe().getReferenceTo().get(0); + } + + fields.add(field); + } + + return fields; + } + + private Schema.DescribeFieldResult getLastFieldDescribe() { + Integer lastFieldIndex = this.fieldChain.size() - 1; + return this.fieldChain[lastFieldIndex].getDescribe(); + } + } +} diff --git a/nebula-logger-plugins/Log-Retention-Rules/plugin/classes/LogRetentionRulesPlugin.cls-meta.xml b/nebula-logger-plugins/Log-Retention-Rules/plugin/classes/LogRetentionRulesPlugin.cls-meta.xml new file mode 100644 index 000000000..f8e5ab635 --- /dev/null +++ b/nebula-logger-plugins/Log-Retention-Rules/plugin/classes/LogRetentionRulesPlugin.cls-meta.xml @@ -0,0 +1,5 @@ + + + 52.0 + Active + diff --git a/nebula-logger-plugins/Log-Retention-Rules/plugin/classes/LogRetentionRulesPlugin_Tests.cls b/nebula-logger-plugins/Log-Retention-Rules/plugin/classes/LogRetentionRulesPlugin_Tests.cls new file mode 100644 index 000000000..4bd6325c8 --- /dev/null +++ b/nebula-logger-plugins/Log-Retention-Rules/plugin/classes/LogRetentionRulesPlugin_Tests.cls @@ -0,0 +1,160 @@ +//-----------------------------------------------------------------------------------------------------------// +// This file is part of the Nebula Logger project, released under the MIT License. // +// The core functionality of this plugin's code originated in https://github.com/jongpie/ApexValidationRules // +// See LICENSE file or go to https://github.com/jongpie/NebulaLogger for full license details. // +//-----------------------------------------------------------------------------------------------------------// + +// TODO revise suppressed PMD rules/clean up code +@SuppressWarnings( + 'PMD.ApexDoc, PMD.AvoidDebugStatements, PMD.ApexAssertionsShouldIncludeMessage, PMD.CyclomaticComplexity, PMD.ExcessiveParameterList, PMD.MethodNamingConventions' +) +@IsTest +private class LogRetentionRulesPlugin_Tests { + @IsTest + static void it_should_set_retention_date_for_rule_with_one_condition() { + enablePlugin(); + Date originalLogRetentionDate = System.today().addDays(Integer.valueOf(Logger.getUserSettings().DefaultNumberOfDaysToRetainLogs__c)); + Integer numberOfDaysToRetainLogs = 90; + Date expectedLogRetentionDate = System.today().addDays(numberOfDaysToRetainLogs); + String scenario = 'Some scenario'; + LogRetentionRule__mdt rule = createMockRule('rule_with_multiple_AND_conditions', numberOfDaysToRetainLogs); + rule.NumberOfDaysToRetainLogs__c = numberOfDaysToRetainLogs; + LogRetentionRulesPlugin.setMockRetentionRule(rule); + List conditions = new List{ + createMockRuleCondition('Log__r.Scenario__c', 'EQUAL_TO', 'Value', scenario) + }; + LogRetentionRulesPlugin.setMockRetentionRuleConditions(rule, conditions); + + Log__c log = new Log__c(Scenario__c = scenario, TransactionId__c = '1234'); + insert log; + log = [SELECT Id, LogRetentionDate__c FROM Log__c WHERE Id = :log.Id]; + System.assertEquals(originalLogRetentionDate, log.LogRetentionDate__c); + + LogEntry__c logEntry = new LogEntry__c(Log__c = log.Id, LoggingLevel__c = LoggingLevel.ERROR.name(), TransactionEntryNumber__c = 1); + insert logEntry; + log = [SELECT Id, LogRetentionDate__c FROM Log__c WHERE Id = :log.Id]; + System.assertEquals(expectedLogRetentionDate, log.LogRetentionDate__c); + } + + @IsTest + static void it_should_set_retention_date_for_rule_with_multiple_and_conditions() { + enablePlugin(); + Date originalLogRetentionDate = System.today().addDays(Integer.valueOf(Logger.getUserSettings().DefaultNumberOfDaysToRetainLogs__c)); + Integer numberOfDaysToRetainLogs = 90; + Date expectedLogRetentionDate = System.today().addDays(numberOfDaysToRetainLogs); + String scenario = 'Some scenario'; + Integer numberOfERRORLogEntries = 1; + LogRetentionRule__mdt rule = createMockRule('rule_with_multiple_AND_conditions', numberOfDaysToRetainLogs); + LogRetentionRulesPlugin.setMockRetentionRule(rule); + List conditions = new List{ + createMockRuleCondition('Log__r.Scenario__c', 'EQUAL_TO', 'Value', scenario), + createMockRuleCondition('Log__r.TotalERRORLogEntries__c', 'GREATER_THAN_OR_EQUAL_TO', 'Value', numberOfERRORLogEntries) + }; + LogRetentionRulesPlugin.setMockRetentionRuleConditions(rule, conditions); + + Log__c log = new Log__c(Scenario__c = scenario, TransactionId__c = '1234'); + insert log; + log = [SELECT Id, LogRetentionDate__c FROM Log__c WHERE Id = :log.Id]; + System.assertEquals(originalLogRetentionDate, log.LogRetentionDate__c); + + LogEntry__c logEntry = new LogEntry__c(Log__c = log.Id, LoggingLevel__c = LoggingLevel.ERROR.name(), TransactionEntryNumber__c = 1); + insert logEntry; + log = [SELECT Id, LogRetentionDate__c, Scenario__c, TotalERRORLogEntries__c FROM Log__c WHERE Id = :log.Id]; + System.assertEquals(expectedLogRetentionDate, log.LogRetentionDate__c, log); + } + + // @IsTest + // static void validateForRuleWithOrConditions() { + // String accountName1 = 'Some account'; + // String accountName2 = 'another account'; + // Account account = new Account(Name = 'Test account'); + // account.Name = accountName1; + + // LogRetentionRule__mdt rule = createMockRule(); + // rule.ConditionLogicType__c = 'OR'; + // List conditions = new List{ + // createMockRuleCondition('Name', 'EQUAL_TO', 'Value', accountName1), + // createMockRuleCondition('Name', 'EQUAL_TO', 'Value', accountName2) + // }; + + // RecordValidator validator = new RecordValidator(account).setRule(rule, conditions); + // List results = validator.validate(false); + // System.assertEquals(1, results.size(), 'Expected 1 validation rule result'); + + // RecordValidator.ValidationRuleResult result = results.get(0); + // System.assertEquals(true, result.hasError, result); + // System.assertEquals(rule.ErrorMessage__c, result.errorMessage, result); + + // try { + // validator.validate(); + // System.assert(false, 'Exception expected on line above'); + // } catch (RecordValidator.RecordValidatorException ex) { + // System.assert(ex.getMessage().contains(rule.ErrorMessage__c), ex); + // } + // } + + // @IsTest + // static void validateForRuleWithCustomConditions() { + // String accountName1 = 'Some account'; + // String accountName2 = 'another account'; + // Integer accountAnnualRevenue = 123000; + // Account account = new Account(Name = 'Test account'); + // account.Name = accountName1; + // account.AnnualRevenue = accountAnnualRevenue; + + // LogRetentionRule__mdt rule = createMockRule(); + // rule.ConditionLogicType__c = 'Custom'; + // rule.CustomConditionLogic__c = '((1 OR 2) AND 3)'; + // List conditions = new List{ + // createMockRuleCondition('Name', 'EQUAL_TO', 'Value', accountName1), + // createMockRuleCondition('Name', 'EQUAL_TO', 'Value', accountName2), + // createMockRuleCondition('AnnualRevenue', 'GREATER_THAN_OR_EQUAL_TO', 'Value', accountAnnualRevenue) + // }; + + // RecordValidator validator = new RecordValidator(account).setRule(rule, conditions); + // List results = validator.validate(false); + // System.assertEquals(1, results.size(), 'Expected 1 validation rule result'); + + // RecordValidator.ValidationRuleResult result = results.get(0); + // System.assertEquals(true, result.hasError, result); + // System.assertEquals(rule.ErrorMessage__c, result.errorMessage, result); + + // try { + // validator.validate(); + // System.assert(false, 'Exception expected on line above'); + // } catch (RecordValidator.RecordValidatorException ex) { + // System.assert(ex.getMessage().contains(rule.ErrorMessage__c), ex); + // } + // } + + static void enablePlugin() { + // Set the plugin's parameters + LoggerPlugin__mdt slackPluginConfig = new LoggerPlugin__mdt( + IsEnabled__c = true, + PluginApiName__c = LogRetentionRulesPlugin.class.getName(), + PluginType__c = 'Apex' + ); + LoggerSObjectHandler.setMockPlugin(Schema.LogEntry__c.SObjectType, slackPluginConfig); + } + + static LogRetentionRule__mdt createMockRule(String developerName, Integer numberOfDaysToRetainLogs) { + return new LogRetentionRule__mdt( + ConditionLogicType__c = 'AND', + CustomConditionLogic__c = null, + DeveloperName = developerName, + IsEnabled__c = true, + NumberOfDaysToRetainLogs__c = numberOfDaysToRetainLogs + ); + } + + static LogRetentionRuleCondition__mdt createMockRuleCondition(String fieldPath, String operator, String valueType, Object value) { + String valueString = value instanceof String ? (String) value : JSON.serialize(value); + return new LogRetentionRuleCondition__mdt( + FieldPath__c = fieldPath, + Operator__c = operator, + SortOrder__c = null, + Value__c = valueString, + ValueType__c = valueType + ); + } +} diff --git a/nebula-logger-plugins/Log-Retention-Rules/plugin/classes/LogRetentionRulesPlugin_Tests.cls-meta.xml b/nebula-logger-plugins/Log-Retention-Rules/plugin/classes/LogRetentionRulesPlugin_Tests.cls-meta.xml new file mode 100644 index 000000000..f8e5ab635 --- /dev/null +++ b/nebula-logger-plugins/Log-Retention-Rules/plugin/classes/LogRetentionRulesPlugin_Tests.cls-meta.xml @@ -0,0 +1,5 @@ + + + 52.0 + Active + diff --git a/nebula-logger-plugins/Log-Retention-Rules/plugin/customMetadata/LoggerPlugin.LogRetentionRules.md-meta.xml b/nebula-logger-plugins/Log-Retention-Rules/plugin/customMetadata/LoggerPlugin.LogRetentionRules.md-meta.xml new file mode 100644 index 000000000..057ffb232 --- /dev/null +++ b/nebula-logger-plugins/Log-Retention-Rules/plugin/customMetadata/LoggerPlugin.LogRetentionRules.md-meta.xml @@ -0,0 +1,27 @@ + + + + false + + Description__c + Adds the ability to create & deploy advanced, configurable rules for setting the retention date of Log__c records, using custom metadata types LogRetentionRule__mdt and LogRetentionRuleCondition__mdt. + + + PluginApiName__c + LogRetentionRulesPlugin + + + PluginType__c + Apex + + + SObjectType__c + LogEntry__c + + diff --git a/nebula-logger-plugins/Log-Retention-Rules/plugin/layouts/LogRetentionRuleCondition__mdt-Log Retention Rule Condition Layout.layout-meta.xml b/nebula-logger-plugins/Log-Retention-Rules/plugin/layouts/LogRetentionRuleCondition__mdt-Log Retention Rule Condition Layout.layout-meta.xml new file mode 100644 index 000000000..e36a1256d --- /dev/null +++ b/nebula-logger-plugins/Log-Retention-Rules/plugin/layouts/LogRetentionRuleCondition__mdt-Log Retention Rule Condition Layout.layout-meta.xml @@ -0,0 +1,105 @@ + + + + false + true + true + + + + Required + MasterLabel + + + Required + DeveloperName + + + + + Required + LogRetentionRule__c + + + Edit + SortOrder__c + + + + + + true + true + true + + + + Required + FieldPath__c + + + Required + Operator__c + + + + + Required + ValueType__c + + + Edit + Value__c + + + + + + false + true + true + + + + Edit + IsProtected + + + Readonly + CreatedById + + + + + Required + NamespacePrefix + + + Readonly + LastModifiedById + + + + + + true + true + false + + + + + + + false + false + false + false + false + + 00h1F000005cUjP + 4 + 0 + Default + + diff --git a/nebula-logger-plugins/Log-Retention-Rules/plugin/layouts/LogRetentionRule__mdt-Log Retention Rule Layout.layout-meta.xml b/nebula-logger-plugins/Log-Retention-Rules/plugin/layouts/LogRetentionRule__mdt-Log Retention Rule Layout.layout-meta.xml new file mode 100644 index 000000000..0b3382852 --- /dev/null +++ b/nebula-logger-plugins/Log-Retention-Rules/plugin/layouts/LogRetentionRule__mdt-Log Retention Rule Layout.layout-meta.xml @@ -0,0 +1,116 @@ + + + + false + true + true + + + + Required + MasterLabel + + + Required + DeveloperName + + + + + Edit + IsEnabled__c + + + Edit + ExecutionOrder__c + + + + + + true + true + true + + + + Edit + NumberOfDaysToRetainLogs__c + + + + + Required + ConditionLogicType__c + + + Edit + CustomConditionLogic__c + + + + + + false + true + true + + + + Edit + IsProtected + + + Readonly + CreatedById + + + + + Edit + NamespacePrefix + + + Readonly + LastModifiedById + + + + + + true + true + false + + + + + + + + NumberOfDaysToRetainLogs__c + + + MasterLabel + DeveloperName + SortOrder__c + FieldPath__c + Operator__c + ValueType__c + Value__c + LogRetentionRuleCondition__mdt.LogRetentionRule__c + SortOrder__c + Asc + + false + false + false + false + false + + 00h17000007xeHS + 4 + 0 + Default + + diff --git a/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRuleCondition__mdt/LogRetentionRuleCondition__mdt.object-meta.xml b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRuleCondition__mdt/LogRetentionRuleCondition__mdt.object-meta.xml new file mode 100644 index 000000000..24cdcfb5d --- /dev/null +++ b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRuleCondition__mdt/LogRetentionRuleCondition__mdt.object-meta.xml @@ -0,0 +1,8 @@ + + + Used to configure field-level conditions for retention rules - each condition checks a LogEntry__c or Log__c field for a specific value, regular expression (regex), or field comparisons. + + Log Retention Rule Conditions + Public + diff --git a/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRuleCondition__mdt/fields/FieldPath__c.field-meta.xml b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRuleCondition__mdt/fields/FieldPath__c.field-meta.xml new file mode 100644 index 000000000..4724e4a9e --- /dev/null +++ b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRuleCondition__mdt/fields/FieldPath__c.field-meta.xml @@ -0,0 +1,12 @@ + + + FieldPath__c + false + SubscriberControlled + The API name of the LogEntryEvent__e field. Note: parent field, such as CreatedBy.Name, are not supported. + + 255 + true + Text + false + diff --git a/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRuleCondition__mdt/fields/LogRetentionRule__c.field-meta.xml b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRuleCondition__mdt/fields/LogRetentionRule__c.field-meta.xml new file mode 100644 index 000000000..d89974c73 --- /dev/null +++ b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRuleCondition__mdt/fields/LogRetentionRule__c.field-meta.xml @@ -0,0 +1,13 @@ + + + LogRetentionRule__c + false + SubscriberControlled + + LogRetentionRule__mdt + Log Retention Rule Conditions + LogRetentionRuleConditions + true + MetadataRelationship + false + diff --git a/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRuleCondition__mdt/fields/Operator__c.field-meta.xml b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRuleCondition__mdt/fields/Operator__c.field-meta.xml new file mode 100644 index 000000000..b62486bba --- /dev/null +++ b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRuleCondition__mdt/fields/Operator__c.field-meta.xml @@ -0,0 +1,60 @@ + + + Operator__c + false + SubscriberControlled + + true + Picklist + + true + + false + + EQUAL_TO + false + + + + NOT_EQUAL_TO + false + + + + LESS_THAN + false + + + + GREATER_THAN + false + + + + LESS_THAN_OR_EQUAL_TO + false + + + + GREATER_THAN_OR_EQUAL_TO + false + + + + STARTS_WITH + false + + + + CONTAINS + false + + + + ENDS_WITH + false + + + + + diff --git a/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRuleCondition__mdt/fields/SortOrder__c.field-meta.xml b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRuleCondition__mdt/fields/SortOrder__c.field-meta.xml new file mode 100644 index 000000000..78d7b35b1 --- /dev/null +++ b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRuleCondition__mdt/fields/SortOrder__c.field-meta.xml @@ -0,0 +1,12 @@ + + + SortOrder__c + false + SubscriberControlled + + 3 + false + 0 + Number + false + diff --git a/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRuleCondition__mdt/fields/ValueType__c.field-meta.xml b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRuleCondition__mdt/fields/ValueType__c.field-meta.xml new file mode 100644 index 000000000..54ea1d6d1 --- /dev/null +++ b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRuleCondition__mdt/fields/ValueType__c.field-meta.xml @@ -0,0 +1,30 @@ + + + ValueType__c + false + SubscriberControlled + + true + Picklist + + true + + false + + Value + false + + + + Field + false + + + + RegEx + false + + + + + diff --git a/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRuleCondition__mdt/fields/Value__c.field-meta.xml b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRuleCondition__mdt/fields/Value__c.field-meta.xml new file mode 100644 index 000000000..0e1738715 --- /dev/null +++ b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRuleCondition__mdt/fields/Value__c.field-meta.xml @@ -0,0 +1,11 @@ + + + Value__c + false + SubscriberControlled + + 255 + false + Text + false + diff --git a/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRuleCondition__mdt/listViews/All.listView-meta.xml b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRuleCondition__mdt/listViews/All.listView-meta.xml new file mode 100644 index 000000000..0b411c06f --- /dev/null +++ b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRuleCondition__mdt/listViews/All.listView-meta.xml @@ -0,0 +1,14 @@ + + + All + MasterLabel + DeveloperName + LogRetentionRule__c + SortOrder__c + FieldPath__c + Operator__c + ValueType__c + Value__c + Everything + + diff --git a/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRule__mdt/LogRetentionRule__mdt.object-meta.xml b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRule__mdt/LogRetentionRule__mdt.object-meta.xml new file mode 100644 index 000000000..95a15408d --- /dev/null +++ b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRule__mdt/LogRetentionRule__mdt.object-meta.xml @@ -0,0 +1,8 @@ + + + Used to configure rules that set the value of Log__c.LogRetentionDate__c. Each rules consists of 1 or more conditions, stored in LogRetentionRuleCondition__mdt. + + Log Retention Rules + Public + diff --git a/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRule__mdt/fields/ConditionLogicType__c.field-meta.xml b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRule__mdt/fields/ConditionLogicType__c.field-meta.xml new file mode 100644 index 000000000..57f1f320f --- /dev/null +++ b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRule__mdt/fields/ConditionLogicType__c.field-meta.xml @@ -0,0 +1,30 @@ + + + ConditionLogicType__c + false + SubscriberControlled + + true + Picklist + + true + + false + + AND + true + + + + OR + false + + + + Custom + false + + + + + diff --git a/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRule__mdt/fields/CustomConditionLogic__c.field-meta.xml b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRule__mdt/fields/CustomConditionLogic__c.field-meta.xml new file mode 100644 index 000000000..cb30f8610 --- /dev/null +++ b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRule__mdt/fields/CustomConditionLogic__c.field-meta.xml @@ -0,0 +1,11 @@ + + + CustomConditionLogic__c + false + SubscriberControlled + + 255 + false + Text + false + diff --git a/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRule__mdt/fields/ExecutionOrder__c.field-meta.xml b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRule__mdt/fields/ExecutionOrder__c.field-meta.xml new file mode 100644 index 000000000..d37bda01f --- /dev/null +++ b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRule__mdt/fields/ExecutionOrder__c.field-meta.xml @@ -0,0 +1,14 @@ + + + ExecutionOrder__c + false + SubscriberControlled + The specific order to execute the retention rules in the org. Rules are executed in order by sorting by execution order, then rule name (ORDER BY ExecutionOrder__c NULLS LAST, DeveloperName). In the event that multiple rules apply to a record, the first matching rule is used to set the log retention date. + + 3 + false + 0 + Number + false + diff --git a/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRule__mdt/fields/IsEnabled__c.field-meta.xml b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRule__mdt/fields/IsEnabled__c.field-meta.xml new file mode 100644 index 000000000..7c3dca299 --- /dev/null +++ b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRule__mdt/fields/IsEnabled__c.field-meta.xml @@ -0,0 +1,9 @@ + + + IsEnabled__c + true + false + SubscriberControlled + + Checkbox + diff --git a/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRule__mdt/fields/NumberOfDaysToRetainLogs__c.field-meta.xml b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRule__mdt/fields/NumberOfDaysToRetainLogs__c.field-meta.xml new file mode 100644 index 000000000..613a84788 --- /dev/null +++ b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRule__mdt/fields/NumberOfDaysToRetainLogs__c.field-meta.xml @@ -0,0 +1,14 @@ + + + NumberOfDaysToRetainLogs__c + false + SubscriberControlled + This value is used to set the field Log__c.LogRetentionDate__c, which is then used by LogBatchPurger to delete old logs. To keep logs indefinitely, set this field to blank (null). + + 18 + false + 0 + Number + false + diff --git a/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRule__mdt/listViews/All.listView-meta.xml b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRule__mdt/listViews/All.listView-meta.xml new file mode 100644 index 000000000..9c1c81673 --- /dev/null +++ b/nebula-logger-plugins/Log-Retention-Rules/plugin/objects/LogRetentionRule__mdt/listViews/All.listView-meta.xml @@ -0,0 +1,11 @@ + + + All + MasterLabel + DeveloperName + IsEnabled__c + ExecutionOrder__c + NumberOfDaysToRetainLogs__c + Everything + + diff --git a/nebula-logger/main/configuration/objects/LoggerScenarioRule__mdt/listViews/All.listView-meta.xml b/nebula-logger/main/configuration/objects/LoggerScenarioRule__mdt/listViews/All.listView-meta.xml index 135b8a2d6..72be322e7 100644 --- a/nebula-logger/main/configuration/objects/LoggerScenarioRule__mdt/listViews/All.listView-meta.xml +++ b/nebula-logger/main/configuration/objects/LoggerScenarioRule__mdt/listViews/All.listView-meta.xml @@ -3,8 +3,8 @@ All MasterLabel DeveloperName - Scenario__c IsEnabled__c + Scenario__c UserLoggingLevelOverride__c NumberOfDaysToRetainLogs__c Everything diff --git a/nebula-logger/main/plugin-framework/layouts/LoggerPlugin__mdt-Logger Plugin Layout.layout-meta.xml b/nebula-logger/main/plugin-framework/layouts/LoggerPlugin__mdt-Logger Plugin Layout.layout-meta.xml index 8468be468..3f2bf15a1 100644 --- a/nebula-logger/main/plugin-framework/layouts/LoggerPlugin__mdt-Logger Plugin Layout.layout-meta.xml +++ b/nebula-logger/main/plugin-framework/layouts/LoggerPlugin__mdt-Logger Plugin Layout.layout-meta.xml @@ -10,13 +10,17 @@ Required MasterLabel - - Required DeveloperName + + + Edit + IsEnabled__c + + @@ -29,6 +33,10 @@ Required SObjectType__c + + Edit + ExecutionOrder__c + @@ -42,6 +50,19 @@ + + true + false + false + + + + Edit + Description__c + + + + false true @@ -85,7 +106,7 @@ false false - 00h1F000005MrCO + 00h17000007ebex 4 0 Default diff --git a/nebula-logger/main/plugin-framework/objects/LoggerPlugin__mdt/listViews/All.listView-meta.xml b/nebula-logger/main/plugin-framework/objects/LoggerPlugin__mdt/listViews/All.listView-meta.xml index 885a3c636..eaa65e95c 100644 --- a/nebula-logger/main/plugin-framework/objects/LoggerPlugin__mdt/listViews/All.listView-meta.xml +++ b/nebula-logger/main/plugin-framework/objects/LoggerPlugin__mdt/listViews/All.listView-meta.xml @@ -4,10 +4,10 @@ MasterLabel DeveloperName IsEnabled__c + ExecutionOrder__c SObjectType__c PluginType__c PluginApiName__c - ExecutionOrder__c Description__c Everything diff --git a/sfdx-project.json b/sfdx-project.json index de1732048..27a67d254 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -13,6 +13,19 @@ "versionDescription": "Added new CMDT LoggerScenarioRule__mdt to override the user's logging level for a particular scenario", "releaseNotesUrl": "https://github.com/jongpie/NebulaLogger/releases" }, + { + "package": "Nebula Logger Plugin - Log Retention Rules", + "path": "nebula-logger-plugins/Log-Retention-Rules/plugin", + "dependencies": [ + { + "package": "Nebula Logger - Unlocked Package@4.6.12-0-logger-scenario-rules" + } + ], + "versionName": "Beta Release", + "versionNumber": "0.9.0.0", + "versionDescription": "Initial beta version of new plugin", + "default": false + }, { "package": "Nebula Logger Plugin - Logger Admin Dashboard", "path": "nebula-logger-plugins/Logger-Admin-Dashboard/plugin", @@ -68,6 +81,7 @@ "Nebula Logger - Unlocked Package@4.6.7-0-security-enhancements": "04t5Y0000015klZQAQ", "Nebula Logger - Unlocked Package@4.6.8-0-new-log-entry-event-stream-component": "04t5Y0000015kplQAA", "Nebula Logger - Unlocked Package@4.6.9-0-custom-metadata-types-optimized": "04t5Y0000015kqtQAA", + "Nebula Logger Plugin - Log Retention Rules": "0Ho5Y000000blNfSAI", "Nebula Logger Plugin - Logger Admin Dashboard": "0Ho5Y000000blNkSAI", "Nebula Logger Plugin - Logger Admin Dashboard@0.9.0-0": "04t5Y0000015l3AQAQ", "Nebula Logger Plugin - Slack": "0Ho5Y000000blMDSAY",