From 13227a223ca9387430513afccbc84c070ddb4c17 Mon Sep 17 00:00:00 2001 From: Heber Date: Sat, 23 Nov 2024 20:15:59 -0700 Subject: [PATCH] Make background jobs respect new disablement features --- dlrs/main/classes/RollupCalculateJob.cls | 20 ++ dlrs/main/classes/RollupCalculateJobTest.cls | 145 ++++++++++++++ dlrs/main/classes/RollupJob.cls | 15 ++ dlrs/main/classes/RollupJobTest.cls | 185 ++++++++++++++++++ dlrs/main/classes/RollupService.cls | 21 +- .../DisableDLRS.customPermission-meta.xml | 6 + .../DisableDLRS.permissionset-meta.xml | 10 + 7 files changed, 399 insertions(+), 3 deletions(-) create mode 100644 unpackaged/config/test/customPermissions/DisableDLRS.customPermission-meta.xml create mode 100644 unpackaged/config/test/permissionsets/DisableDLRS.permissionset-meta.xml diff --git a/dlrs/main/classes/RollupCalculateJob.cls b/dlrs/main/classes/RollupCalculateJob.cls index b4796b46..41dbe1a3 100644 --- a/dlrs/main/classes/RollupCalculateJob.cls +++ b/dlrs/main/classes/RollupCalculateJob.cls @@ -41,6 +41,26 @@ public with sharing class RollupCalculateJob implements Database.Batchable lookups = new RollupSummariesSelector() + .selectById(new Set{ (String) lookupId }); + + if (lookups.size() == 0) { + throw RollupServiceException.rollupNotFound(lookupId); + } + + RollupSummary lookup = lookups[0]; + + if ( + Utilities.userHasCustomPermission(lookup.BypassCustPermApiName) || + BypassHandler.isBypassed(lookup.UniqueName) + ) { + System.debug('Rollup is disabled, will not execute ' + lookupId); + // return an "empty" iteration so it doesn't run the execute method + return Database.getQueryLocator( + 'SELECT Id FROM ' + lookup.ParentObject + ' LIMIT 0' + ); + } + // Query all the parent records as per the lookup definition return RollupService.masterRecordsAsQueryLocator( lookupId, diff --git a/dlrs/main/classes/RollupCalculateJobTest.cls b/dlrs/main/classes/RollupCalculateJobTest.cls index 2ef8d296..a0840f31 100644 --- a/dlrs/main/classes/RollupCalculateJobTest.cls +++ b/dlrs/main/classes/RollupCalculateJobTest.cls @@ -129,6 +129,151 @@ private class RollupCalculateJobTest { Assert.areEqual(0, logs.size(), 'Found:' + JSON.serializePretty(logs)); } + @IsTest + static void testRunBatchWithGlobalDisable() { + String prefix = LookupRollupSummary2__mdt.sObjectType.getDescribe() + .getKeyPrefix(); + + LookupRollupSummary2__mdt rollupCfg = new LookupRollupSummary2__mdt( + Id = prefix + '00000000000000D', + Label = 'A Summary', + DeveloperName = 'A_Summary', + ParentObject__c = 'Account', + ChildObject__c = 'Contact', + RelationshipField__c = 'AccountId', + AggregateOperation__c = RollupSummaries.AggregateOperation.Count.name(), + AggregateResultField__c = 'Description', + FieldToAggregate__c = 'Id', + CalculationMode__c = 'Realtime', + AggregateAllRows__c = false, + Active__c = true + ); + RollupSummariesSelector.setRollupCache( + false, + false, + RollupSummary.toList(new List{ rollupCfg }) + ); + + // globally disable DLRS + DeclarativeLookupRollupSummaries__c settings = new DeclarativeLookupRollupSummaries__c( + DisableDLRSGlobally__c = true + ); + insert settings; + + Account a = new Account(Name = 'Test'); + insert a; + + RollupCalculateJob job = new RollupCalculateJob(rollupCfg.Id, 'Id != NULL'); + Test.startTest(); + String jobId = Database.executeBatch(job); + Test.stopTest(); + + AsyncApexJob asyncJob = [ + SELECT Id, Status, JobItemsProcessed, TotalJobItems + FROM AsyncApexJob + WHERE Id = :jobId + ]; + + Assert.areEqual('Completed', asyncJob.Status); + Assert.areEqual(0, asyncJob.JobItemsProcessed); + Assert.areEqual(0, asyncJob.TotalJobItems); + + List logs = [ + SELECT Id, ParentId__c, ParentObject__c, ErrorMessage__c + FROM LookupRollupSummaryLog__c + ]; + Assert.areEqual(0, logs.size(), 'Found:' + JSON.serializePretty(logs)); + } + + @IsTest + static void testRunBatchWithCustPermDisable() { + // find the profile that has access to the Custom Permission we want to use to check (if it even exists in the system) + List permSetsWithAccess = [ + SELECT ParentId + FROM SetupEntityAccess + WHERE + SetupEntityId IN ( + SELECT Id + FROM CustomPermission + WHERE DeveloperName = 'DisableDLRS' + ) + AND Parent.IsOwnedByProfile = FALSE + ]; + if (permSetsWithAccess.isEmpty()) { + return; // this org doesn't have the necessary metadata to test this feature + } + // see if the running user already has that permission set + List assignments = [ + SELECT Id + FROM PermissionSetAssignment + WHERE + AssigneeId = :UserInfo.getUserId() + AND PermissionSetId = :permSetsWithAccess[0].ParentId + ]; + if (assignments.isEmpty()) { + // user doesn't have the necessary perm set to grant it to them. + System.runAs(new User(Id = UserInfo.getUserId())) { + insert new PermissionSetAssignment( + AssigneeId = UserInfo.getUserId(), + PermissionSetId = permSetsWithAccess[0].ParentId + ); + } + } + + String prefix = LookupRollupSummary2__mdt.sObjectType.getDescribe() + .getKeyPrefix(); + + LookupRollupSummary2__mdt rollupCfg = new LookupRollupSummary2__mdt( + Id = prefix + '00000000000000D', + Label = 'A Summary', + DeveloperName = 'A_Summary', + ParentObject__c = 'Account', + ChildObject__c = 'Contact', + RelationshipField__c = 'AccountId', + AggregateOperation__c = RollupSummaries.AggregateOperation.Count.name(), + AggregateResultField__c = 'Description', + FieldToAggregate__c = 'Id', + CalculationMode__c = 'Realtime', + AggregateAllRows__c = false, + Active__c = true, + BypassPermissionApiName__c = 'DisableDLRS' + ); + RollupSummariesSelector.setRollupCache( + false, + false, + RollupSummary.toList(new List{ rollupCfg }) + ); + + Account a = new Account(Name = 'Test'); + insert a; + + RollupCalculateJob job = new RollupCalculateJob(rollupCfg.Id, 'Id != NULL'); + String jobId; + System.runAs(new User(Id = UserInfo.getUserId())) { + Test.startTest(); + Assert.isTrue(FeatureManagement.checkPermission('DisableDLRS')); + // go into runAs because we need to get the perms recalculated + jobId = Database.executeBatch(job); + Test.stopTest(); + } + + AsyncApexJob asyncJob = [ + SELECT Id, Status, JobItemsProcessed, TotalJobItems + FROM AsyncApexJob + WHERE Id = :jobId + ]; + + Assert.areEqual('Completed', asyncJob.Status); + Assert.areEqual(0, asyncJob.JobItemsProcessed); + Assert.areEqual(0, asyncJob.TotalJobItems); + + List logs = [ + SELECT Id, ParentId__c, ParentObject__c, ErrorMessage__c + FROM LookupRollupSummaryLog__c + ]; + Assert.areEqual(0, logs.size(), 'Found:' + JSON.serializePretty(logs)); + } + public class MockBatchableContext implements Database.BatchableContext { public Id getJobId() { return '100000000000000'; diff --git a/dlrs/main/classes/RollupJob.cls b/dlrs/main/classes/RollupJob.cls index 6c79a2cc..d66d770e 100644 --- a/dlrs/main/classes/RollupJob.cls +++ b/dlrs/main/classes/RollupJob.cls @@ -34,6 +34,21 @@ global with sharing class RollupJob implements Schedulable, Database.Batchable{ rollupCfg }) + ); + + Account a = new Account(Name = 'Test'); + insert a; + + // globally disable DLRS + DeclarativeLookupRollupSummaries__c settings = new DeclarativeLookupRollupSummaries__c( + DisableDLRSGlobally__c = true + ); + insert settings; + + List items = new List(); + + LookupRollupSummaryScheduleItems__c scheduledItem = new LookupRollupSummaryScheduleItems__c(); + scheduledItem.Name = a.Id; + scheduledItem.LookupRollupSummary2__c = rollupCfg.Id; + scheduledItem.ParentId__c = a.Id; + scheduledItem.QualifiedParentID__c = a.Id + '#' + rollupCfg.Id; + + items.add(scheduledItem); + + insert items; + + RollupJob job = new RollupJob(); + Test.startTest(); + String jobId = Database.executeBatch(job); + Test.stopTest(); + + AsyncApexJob asyncJob = [ + SELECT Id, Status, JobItemsProcessed, TotalJobItems + FROM AsyncApexJob + WHERE Id = :jobId + ]; + + Assert.areEqual('Completed', asyncJob.Status); + Assert.areEqual(0, asyncJob.JobItemsProcessed); + Assert.areEqual(0, asyncJob.TotalJobItems); + + List logs = [ + SELECT Id, ParentId__c, ParentObject__c + FROM LookupRollupSummaryLog__c + ]; + Assert.areEqual(0, logs.size()); + } + + @IsTest + static void testDisabledSpecificRollupRunJob() { + // find the profile that has access to the Custom Permission we want to use to check (if it even exists in the system) + List permSetsWithAccess = [ + SELECT ParentId + FROM SetupEntityAccess + WHERE + SetupEntityId IN ( + SELECT Id + FROM CustomPermission + WHERE DeveloperName = 'DisableDLRS' + ) + AND Parent.IsOwnedByProfile = FALSE + ]; + if (permSetsWithAccess.isEmpty()) { + return; // this org doesn't have the necessary metadata to test this feature + } + // see if the running user already has that permission set + List assignments = [ + SELECT Id + FROM PermissionSetAssignment + WHERE + AssigneeId = :UserInfo.getUserId() + AND PermissionSetId = :permSetsWithAccess[0].ParentId + ]; + if (assignments.isEmpty()) { + // user doesn't have the necessary perm set to grant it to them. + System.runAs(new User(Id = UserInfo.getUserId())) { + insert new PermissionSetAssignment( + AssigneeId = UserInfo.getUserId(), + PermissionSetId = permSetsWithAccess[0].ParentId + ); + } + } + String prefix = LookupRollupSummary2__mdt.sObjectType.getDescribe() + .getKeyPrefix(); + + LookupRollupSummary2__mdt rollupCfg = new LookupRollupSummary2__mdt( + Id = prefix + '00000000000000D', + Label = 'A Summary', + DeveloperName = 'A_Summary', + ParentObject__c = 'Account', + ChildObject__c = 'Contact', + RelationshipField__c = 'AccountId', + AggregateOperation__c = RollupSummaries.AggregateOperation.Count.name(), + AggregateResultField__c = 'NumberOfEmployees', + FieldToAggregate__c = 'Id', + CalculationMode__c = 'Realtime', + AggregateAllRows__c = false, + Active__c = true, + BypassPermissionApiName__c = 'DisableDLRS' + ); + + RollupSummariesSelector.setRollupCache( + false, + false, + RollupSummary.toList(new List{ rollupCfg }) + ); + + Account a = new Account(Name = 'Test'); + insert a; + + Contact c = new Contact(LastName = 'Test', AccountId = a.Id); + insert c; + + List items = new List(); + + LookupRollupSummaryScheduleItems__c scheduledItem = new LookupRollupSummaryScheduleItems__c(); + scheduledItem.Name = a.Id; + scheduledItem.LookupRollupSummary2__c = rollupCfg.Id; + scheduledItem.ParentId__c = a.Id; + scheduledItem.QualifiedParentID__c = a.Id + '#' + rollupCfg.Id; + + items.add(scheduledItem); + + insert items; + + String jobId; + System.runAs(new User(Id = UserInfo.getUserId())) { + Test.startTest(); + Assert.isTrue(FeatureManagement.checkPermission('DisableDLRS')); + // go into runAs because we need to get the perms recalculated + jobId = Database.executeBatch(new RollupJob()); + Test.stopTest(); + } + + AsyncApexJob asyncJob = [ + SELECT Id, Status, JobItemsProcessed, TotalJobItems + FROM AsyncApexJob + WHERE Id = :jobId + ]; + + Assert.areEqual('Completed', asyncJob.Status); + Assert.areEqual(1, asyncJob.JobItemsProcessed); + Assert.areEqual(1, asyncJob.TotalJobItems); + + a = [SELECT Id, Description FROM Account WHERE Id = :a.Id]; + + Assert.isNull(a.Description); + + items = [ + SELECT Id, ParentId__c, LookupRollupSummary2__c + FROM LookupRollupSummaryScheduleItems__c + ]; + Assert.isTrue( + items.isEmpty(), + 'Expected empty but found' + JSON.serialize(items) + ); + + List logs = [ + SELECT Id, ParentId__c, ParentObject__c + FROM LookupRollupSummaryLog__c + ]; + Assert.areEqual(0, logs.size()); + } + @IsTest static void testFailureWithEmail() { String prefix = LookupRollupSummary2__mdt.sObjectType.getDescribe() diff --git a/dlrs/main/classes/RollupService.cls b/dlrs/main/classes/RollupService.cls index cbd10777..85db874d 100644 --- a/dlrs/main/classes/RollupService.cls +++ b/dlrs/main/classes/RollupService.cls @@ -96,6 +96,14 @@ global with sharing class RollupService { // Already running? checkJobAlreadyRunning(lookupId, lookup.Name); + if ( + DeclarativeLookupRollupSummaries__c.getInstance() + .DisableDLRSGlobally__c == true + ) { + throw new RollupServiceException( + 'DLRS is disabled through Custom Settings, unable to run job.' + ); + } // Already active? if ( (lookup.Active == null || lookup.Active == false) && @@ -396,7 +404,7 @@ global with sharing class RollupService { } /** - * Clears the bypass of aall rollups. + * Clears the bypass of all rollups. */ global static void clearAllBypasses() { BypassHandler.clearAllBypasses(); @@ -498,10 +506,17 @@ global with sharing class RollupService { } else { lookup = lookups.get(scheduleItem.LookupRollupSummary2__c); } - if (lookup == null) { + + if ( + // sched item is for a non-existent rollup definition + lookup == null || + // running user has a custom perm that disables this rollup + Utilities.userHasCustomPermission(lookup.BypassCustPermApiName) + ) { + // do not process this item, item will still be deleted continue; } - // The lookup definition could have been changed or due to a historic bug in correctly associated + // The lookup definition could have been changed or due to a historic bug incorrectly associated if (parentId.getSobjectType() != gd.get(lookup.ParentObject)) continue; Set parentIds = parentIdsByParentType.get(lookup.ParentObject); diff --git a/unpackaged/config/test/customPermissions/DisableDLRS.customPermission-meta.xml b/unpackaged/config/test/customPermissions/DisableDLRS.customPermission-meta.xml new file mode 100644 index 00000000..273dd352 --- /dev/null +++ b/unpackaged/config/test/customPermissions/DisableDLRS.customPermission-meta.xml @@ -0,0 +1,6 @@ + + + Used to disable specific rollup jobs for specific users + false + + diff --git a/unpackaged/config/test/permissionsets/DisableDLRS.permissionset-meta.xml b/unpackaged/config/test/permissionsets/DisableDLRS.permissionset-meta.xml new file mode 100644 index 00000000..d1a905cb --- /dev/null +++ b/unpackaged/config/test/permissionsets/DisableDLRS.permissionset-meta.xml @@ -0,0 +1,10 @@ + + + + true + DisableDLRS + + Used in Apex Testing to disable individual DLRS rollups + false + +