Skip to content

Commit

Permalink
Merge pull request #1494 from SFDO-Community/feature/786-add-global-b…
Browse files Browse the repository at this point in the history
…ypass

Allow greater control to disable DLRS globally or in specific cases
  • Loading branch information
aheber authored Nov 24, 2024
2 parents e9f9de6 + eb0967c commit 2ed442e
Show file tree
Hide file tree
Showing 23 changed files with 667 additions and 72 deletions.
26 changes: 14 additions & 12 deletions dlrs/main/classes/BypassHandler.cls
Original file line number Diff line number Diff line change
Expand Up @@ -31,30 +31,31 @@
* The bypass and removebypass method return the result of the default Set object operations.
**/
public without sharing class BypassHandler {
private static Set<String> bypassedRollups;
private static Set<String> bypassedRollups = new Set<String>();
private static Boolean bypassAll = false;

/**
* Initialize the set if necessary for adding rollups to the bypass list.
* Checks if the rollup is bypassed or not. Returns true if it is. False otherwise.
* Could be bypassed by custom setting, bypass all, or specific named bypass
*/
private static void init() {
if (bypassedRollups == null) {
bypassedRollups = new Set<String>();
}
public static Boolean isBypassed(String handlerName) {
return DeclarativeLookupRollupSummaries__c.getInstance()
.DisableDLRSGlobally__c == true ||
bypassAll ||
bypassedRollups.contains(handlerName);
}

/**
* Checks if the rollup is bypassed or not. Returns true if it is. False otherwise.
* Sets a global bypass value, if true all rollups will be disabled for execution
*/
public static Boolean isBypassed(String handlerName) {
return bypassedRollups != null && bypassedRollups.contains(handlerName);
public static void setBypassAll(Boolean val) {
bypassAll = val;
}

/**
* Adds a rollup to the bypassed rollups list.
*/
public static Boolean bypass(String handlerName) {
init();

if (handlerName != null) {
System.debug(
LoggingLevel.INFO,
Expand All @@ -75,7 +76,7 @@ public without sharing class BypassHandler {
* Clears the bypass for a single rollup.
*/
public static Boolean clearBypass(String handlerName) {
if (bypassedRollups != null && handlerName != null) {
if (handlerName != null) {
System.debug(
LoggingLevel.INFO,
'DLRS trigger handler is no longer bypassed: ' + handlerName
Expand All @@ -95,6 +96,7 @@ public without sharing class BypassHandler {
* Clears all bypasses, if any.
*/
public static void clearAllBypasses() {
bypassAll = false;
if (bypassedRollups != null) {
bypassedRollups.clear();
}
Expand Down
83 changes: 57 additions & 26 deletions dlrs/main/classes/BypassHandlerTest.cls
Original file line number Diff line number Diff line change
Expand Up @@ -28,54 +28,85 @@ private class BypassHandlerTest {
@IsTest
static void testApi() {
String rollupUniqueName = 'SampleRollup';
Boolean bypassResult;

Test.startTest();
System.assertEquals(
false,
Assert.isFalse(
BypassHandler.isBypassed(rollupUniqueName),
'The rollup should not be bypassed yet.'
);
bypassResult = BypassHandler.bypass(rollupUniqueName);
System.assert(
bypassResult,

Assert.isTrue(
BypassHandler.bypass(rollupUniqueName),
'Should have modified the bypassed rollups set.'
);
System.assertEquals(
true,
Assert.isTrue(
BypassHandler.isBypassed(rollupUniqueName),
'The rollup should be bypassed.'
);
bypassResult = BypassHandler.clearBypass(rollupUniqueName);
System.assert(
bypassResult,

Assert.isTrue(
BypassHandler.clearBypass(rollupUniqueName),
'Should have modified the bypassed rollups set.'
);
System.assertEquals(
false,
Assert.isFalse(
BypassHandler.isBypassed(rollupUniqueName),
'The rollup should not be bypassed anymore.'
);
BypassHandler.bypass(rollupUniqueName);
BypassHandler.clearAllBypasses();
System.assertEquals(
false,
Assert.isFalse(
BypassHandler.isBypassed(rollupUniqueName),
'The rollup should not be bypassed anymore.'
);

bypassResult = BypassHandler.bypass(null);
System.assertEquals(
false,
bypassResult,
Assert.isFalse(
BypassHandler.bypass(null),
'Should return "false" for a null rollup name.'
);
bypassResult = BypassHandler.clearBypass(null);
System.assertEquals(
false,
bypassResult,

Assert.isFalse(
BypassHandler.clearBypass(null),
'Should return "false" for a null rollup name.'
);
Test.stopTest();

BypassHandler.setBypassAll(true);
Assert.isTrue(
BypassHandler.isBypassed(rollupUniqueName),
'Should return "true" for all rollup names.'
);
Assert.isTrue(
BypassHandler.isBypassed('new name'),
'Should return "true" for all rollup names.'
);
BypassHandler.setBypassAll(false);

Assert.isFalse(
BypassHandler.isBypassed(rollupUniqueName),
'Should return "false" for all rollup names.'
);
Assert.isFalse(
BypassHandler.isBypassed('new name'),
'Should return "false" for all rollup names.'
);
BypassHandler.setBypassAll(true);
Assert.isTrue(
BypassHandler.isBypassed('new name'),
'Should return "true" for all rollup names.'
);
BypassHandler.clearAllBypasses();
Assert.isFalse(
BypassHandler.isBypassed('new name'),
'Should return "false" for all rollup names.'
);
}

@IsTest
static void testCustomSettingDisable() {
String rollupUniqueName = 'Rollup1';
Assert.isFalse(BypassHandler.isBypassed(rollupUniqueName));

DeclarativeLookupRollupSummaries__c settings = DeclarativeLookupRollupSummaries__c.getInstance();
settings.DisableDLRSGlobally__c = true;
insert settings;

Assert.isTrue(BypassHandler.isBypassed(rollupUniqueName));
}
}
20 changes: 20 additions & 0 deletions dlrs/main/classes/RollupCalculateJob.cls
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,26 @@ public with sharing class RollupCalculateJob implements Database.Batchable<sObje
}

public Database.QueryLocator start(Database.BatchableContext BC) {
List<RollupSummary> lookups = new RollupSummariesSelector()
.selectById(new Set<String>{ (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,
Expand Down
145 changes: 145 additions & 0 deletions dlrs/main/classes/RollupCalculateJobTest.cls
Original file line number Diff line number Diff line change
Expand Up @@ -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<LookupRollupSummary2__mdt>{ 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<LookupRollupSummaryLog__c> 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<SetupEntityAccess> 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<PermissionSetAssignment> 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<LookupRollupSummary2__mdt>{ 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<LookupRollupSummaryLog__c> 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';
Expand Down
4 changes: 4 additions & 0 deletions dlrs/main/classes/RollupEditorController.cls
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,8 @@ public with sharing class RollupEditorController {
@AuraEnabled
public String aggregateResultField;
@AuraEnabled
public String bypassPermissionApiName;
@AuraEnabled
public String calculationMode;
@AuraEnabled
public String calculationSharingMode;
Expand Down Expand Up @@ -309,6 +311,7 @@ public with sharing class RollupEditorController {
this.aggregateAllRows = record.AggregateAllRows__c;
this.aggregateOperation = record.AggregateOperation__c;
this.aggregateResultField = record.AggregateResultField__c;
this.bypassPermissionApiName = record.BypassPermissionApiName__c;
this.calculationMode = record.CalculationMode__c;
this.calculationSharingMode = record.CalculationSharingMode__c;
this.childObject = record.ChildObject__c;
Expand All @@ -335,6 +338,7 @@ public with sharing class RollupEditorController {
record.AggregateAllRows__c = this.aggregateAllRows;
record.AggregateOperation__c = this.aggregateOperation;
record.AggregateResultField__c = this.aggregateResultField;
record.BypassPermissionApiName__c = this.bypassPermissionApiName;
record.CalculationMode__c = this.calculationMode;
record.CalculationSharingMode__c = this.calculationSharingMode;
record.ChildObject__c = this.childObject;
Expand Down
15 changes: 15 additions & 0 deletions dlrs/main/classes/RollupJob.cls
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,21 @@ global with sharing class RollupJob implements Schedulable, Database.Batchable<s
}

public Database.QueryLocator start(Database.BatchableContext BC) {
if (
DeclarativeLookupRollupSummaries__c.getInstance()
.DisableDLRSGlobally__c == true
) {
System.debug('DLRS is disabled, will not execute');
// return an "empty" iteration so it doesn't run the execute method
return Database.getQueryLocator(
[
SELECT Id
FROM LookupRollupSummaryScheduleItems__c
LIMIT 0
]
);
}

// Query all the currently available scheduled records indicating records requiring rollups
return new RollupSummaryScheduleItemsSelector().selectAllQueryLocator();
}
Expand Down
Loading

0 comments on commit 2ed442e

Please sign in to comment.