Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow greater control to disable DLRS globally or in specific cases #1494

Merged
merged 2 commits into from
Nov 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading