Skip to content

Commit

Permalink
v1.6.2 - Intermediate Grandparent Rollup bugfixes (#534)
Browse files Browse the repository at this point in the history
* Fixes #514 by retrieving now-orphaned grandparent records when an intermediate lookup is changed and there are no further connections to the grandparent

* Fixing DST literals

* Adds additional logging to assist with #523

* Updating Is Full Record Set helptext

* Fixes #530 by detecting intermediate grandparent deletions
  • Loading branch information
jamessimone authored Nov 11, 2023
1 parent eabb2f5 commit 86a0651
Show file tree
Hide file tree
Showing 16 changed files with 231 additions and 43 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ As well, don't miss [the Wiki](../../wiki), which includes even more info for co

## Deployment & Setup

<a href="https://login.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008OZwYAAW">
<a href="https://login.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008Oa7TAAS">
<img alt="Deploy to Salesforce"
src="./media/deploy-package-to-prod.png">
</a>

<a href="https://test.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008OZwYAAW">
<a href="https://test.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008Oa7TAAS">
<img alt="Deploy to Salesforce Sandbox"
src="./media/deploy-package-to-sandbox.png">
</a>
Expand Down
57 changes: 57 additions & 0 deletions extra-tests/classes/RollupEvaluatorTests.cls
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,63 @@ private class RollupEvaluatorTests {
System.assertEquals(true, eval.matches(oppFive), 'Id matches inner nested conditional!');
}

@IsTest
static void nestedConditionalWorksWithInSpaces() {
String whereClause = 'PrivacyConsentStatus = \'Yes\' and EffectiveTo <= TODAY and Effectivefrom >= TODAY and CaptureSource in ( \'Hi\' , \'Hello\' , \'Hola\') and (Name = \'excellent\' or Name = \'stellar\')';

ContactPointConsent match1 = new ContactPointConsent(
PrivacyConsentStatus = 'Yes',
EffectiveTo = System.now().addDays(-1),
EffectiveFrom = System.now(),
CaptureSource = 'Hi',
Name = 'Excellent'
);
ContactPointConsent match2 = new ContactPointConsent(
PrivacyConsentStatus = 'Yes',
EffectiveTo = System.now().addDays(-1),
EffectiveFrom = System.now(),
CaptureSource = 'hello',
Name = 'stellar'
);
ContactPointConsent match3 = new ContactPointConsent(
PrivacyConsentStatus = 'Yes',
EffectiveTo = System.now().addDays(-1),
EffectiveFrom = System.now(),
CaptureSource = 'Hola',
Name = 'stellar'
);
ContactPointConsent nonMatch1 = new ContactPointConsent(
PrivacyConsentStatus = 'nonmatch',
EffectiveTo = System.now().addDays(2),
EffectiveFrom = System.now().addDays(-1),
CaptureSource = 'Hi',
Name = 'stellar'
);
ContactPointConsent nonMatch2 = new ContactPointConsent(
PrivacyConsentStatus = 'Yes',
EffectiveTo = System.now(),
EffectiveFrom = System.now().addDays(-1),
CaptureSource = 'nonmatch',
Name = 'stellar'
);
ContactPointConsent nonMatch3 = new ContactPointConsent(
PrivacyConsentStatus = 'Yes',
EffectiveTo = System.now(),
EffectiveFrom = System.now().addDays(-1),
CaptureSource = 'hi',
Name = 'nonmatch'
);

Rollup.Evaluator eval = new RollupEvaluator.WhereFieldEvaluator(whereClause, ContactPointConsent.SObjectType);

System.assertEquals(true, eval.matches(match1));
System.assertEquals(true, eval.matches(match2));
System.assertEquals(true, eval.matches(match3));
System.assertEquals(false, eval.matches(nonMatch1));
System.assertEquals(false, eval.matches(nonMatch2));
System.assertEquals(false, eval.matches(nonMatch3));
}

private static String getSoqlCompliantDatetime(Datetime dt) {
return dt.format('yyyy-MM-dd\'T\'HH:mm:ssZ');
}
Expand Down
43 changes: 40 additions & 3 deletions extra-tests/classes/RollupFlowBulkProcessorTests.cls
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,46 @@ private class RollupFlowBulkProcessorTests {

newGrandparent = [SELECT NaicsDesc FROM Account WHERE Id = :newGrandparent.Id];
System.assertEquals(grandchild.Name, newGrandparent.NaicsDesc);
// TODO - continue WIP to fully fix the reset part
// oldGrandparent = [SELECT NaicsDesc FROM Account WHERE Id = :oldGrandparent.Id];
// System.assertEquals(null, oldGrandparent.NaicsDesc, 'Old grandparent value should have been cleared');
oldGrandparent = [SELECT NaicsDesc FROM Account WHERE Id = :oldGrandparent.Id];
System.assertEquals(null, oldGrandparent.NaicsDesc, 'Old grandparent value should have been cleared');
}

@IsTest
static void shouldClearGrandparentForIntermediateParentDeletion() {
Account oldGrandparent = [SELECT Id, Name FROM Account];
oldGrandparent.NaicsDesc = oldGrandparent.Name;
update oldGrandparent;
Rollup.onlyUseMockMetadata = true;
Rollup.rollupMetadata = new List<Rollup__mdt>{
new Rollup__mdt(
RollupOperation__c = 'CONCAT',
CalcItem__c = 'ContactPointAddress',
LookupObject__c = 'Account',
RollupFieldOnCalcItem__c = 'Name',
LookupFieldOnCalcItem__c = 'ContactPointPhoneId',
LookupFieldOnLookupObject__c = 'Id',
RollupFieldOnLookupObject__c = 'NaicsDesc',
GrandparentRelationshipFieldPath__c = 'ContactPointPhone.Parent.Name'
)
};
ContactPointPhone parent = new ContactPointPhone(TelephoneNumber = 'Parent Again', ParentId = oldGrandparent.Id);
insert parent;
ContactPointAddress grandchild = new ContactPointAddress(Name = 'Hello Again', ContactPointPhoneId = parent.Id);
insert grandchild;

delete parent;

RollupFlowBulkProcessor.FlowInput input = new RollupFlowBulkProcessor.FlowInput();
input.recordsToRollup = new List<SObject>{ parent };
input.rollupContext = 'DELETE';
input.deferProcessing = false;

Test.startTest();
RollupFlowBulkProcessor.addRollup(new List<RollupFlowBulkProcessor.FlowInput>{ input });
Test.stopTest();

oldGrandparent = [SELECT NaicsDesc FROM Account WHERE Id = :oldGrandparent.Id];
System.assertEquals(null, oldGrandparent.NaicsDesc, 'Old grandparent value should have been cleared');
}

@IsTest
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "apex-rollup",
"version": "1.6.1",
"version": "1.6.2",
"description": "Fast, configurable, elastically scaling custom rollup solution. Apex Invocable action, one-liner Apex trigger/CMDT-driven logic, and scheduled Apex-ready.",
"repository": {
"type": "git",
Expand Down
4 changes: 2 additions & 2 deletions rollup-namespaced/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ For more info, see the base `README`.

## Deployment & Setup

<a href="https://login.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008OZwdAAG">
<a href="https://login.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008Oa7YAAS">
<img alt="Deploy to Salesforce"
src="./media/deploy-package-to-prod.png">
</a>

<a href="https://test.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008OZwdAAG">
<a href="https://test.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008Oa7YAAS">
<img alt="Deploy to Salesforce Sandbox"
src="./media/deploy-package-to-sandbox.png">
</a>
7 changes: 4 additions & 3 deletions rollup-namespaced/sfdx-project.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"default": true,
"package": "apex-rollup-namespaced",
"path": "rollup-namespaced/source/rollup",
"versionName": "Fixes wildcard support for LIKE where clauses. Adds support for intermediate rollup reparenting detection in Flow.",
"versionNumber": "1.1.1.0",
"versionName": "Intermediate grandparent reparenting/deletion now properly recalculates grandparent rollups",
"versionNumber": "1.1.2.0",
"versionDescription": "Fast, configurable, elastically scaling custom rollup solution. Apex Invocable action, one-liner Apex trigger/CMDT-driven logic, and scheduled Apex-ready.",
"releaseNotesUrl": "https://github.com/jamessimone/apex-rollup/releases/latest",
"unpackagedMetadata": {
Expand All @@ -25,6 +25,7 @@
"apex-rollup-namespaced@1.0.52-0": "04t6g000008C72GAAS",
"apex-rollup-namespaced@1.0.53-0": "04t6g000008C739AAC",
"apex-rollup-namespaced@1.1.0-0": "04t6g000008C7AVAA0",
"apex-rollup-namespaced@1.1.1-0": "04t6g000008OZwdAAG"
"apex-rollup-namespaced@1.1.1-0": "04t6g000008OZwdAAG",
"apex-rollup-namespaced@1.1.2-0": "04t6g000008Oa7YAAS"
}
}
33 changes: 24 additions & 9 deletions rollup/core/classes/Rollup.cls
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ global without sharing virtual class Rollup implements RollupLogger.ToStringObje
}

private class RollupMetadata {
public final Map<Schema.SObjectType, Set<String>> typeToOldIntermediateParents = new Map<Schema.SObjectType, Set<String>>();
public final Map<Schema.SObjectType, Set<Id>> typeToOldIntermediateParents = new Map<Schema.SObjectType, Set<Id>>();
public final Set<String> recordIds = new Set<String>();
public final Set<String> queryFields = new Set<String>();
public final List<Rollup__mdt> metadata = new List<Rollup__mdt>();
Expand Down Expand Up @@ -2081,7 +2081,7 @@ global without sharing virtual class Rollup implements RollupLogger.ToStringObje
} else if (meta.IsRollupStartedFromParent__c) {
queryWrapper = getParentQueryWrapper(calcItems, oldCalcItems, meta);
} else if (isIntermediateRollupForGrandparent) {
queryWrapper = getIntermediateGrandparentQueryWrapper(meta, calcItems, oldCalcItems);
queryWrapper = getIntermediateGrandparentQueryWrapper(meta, calcItems, oldCalcItems, rollupContext);
}

if (queryWrapper?.hasQuery == true) {
Expand Down Expand Up @@ -2376,6 +2376,10 @@ global without sharing virtual class Rollup implements RollupLogger.ToStringObje
fullRecalc.storeParentFieldsToClear(wrappedMeta.parentRecordsToReset);
fullRecalc.isNoOp = false;
}
if (wrappedMeta.typeToOldIntermediateParents.isEmpty() == false) {
fullRecalc.setOldIntermediateGrandparents(wrappedMeta.typeToOldIntermediateParents);
fullRecalc.isNoOp = false;
}
return fullRecalc;
}

Expand Down Expand Up @@ -2498,8 +2502,14 @@ global without sharing virtual class Rollup implements RollupLogger.ToStringObje
throw ex;
}

private static QueryWrapper getIntermediateGrandparentQueryWrapper(Rollup__mdt meta, List<SObject> calcItems, Map<Id, SObject> oldCalcItems) {
if (isCDC || oldCalcItems?.isEmpty() != false) {
private static QueryWrapper getIntermediateGrandparentQueryWrapper(
Rollup__mdt meta,
List<SObject> calcItems,
Map<Id, SObject> oldCalcItems,
String rollupContext
) {
Boolean isDelete = rollupContext.startsWithIgnoreCase('delete');
if (isCDC || oldCalcItems?.isEmpty() != false && isDelete == false) {
return new QueryWrapper();
}

Expand All @@ -2519,21 +2529,26 @@ global without sharing virtual class Rollup implements RollupLogger.ToStringObje
}

QueryWrapper wrapper = new QueryWrapper('', priorFieldPath);
SObject possibleOldItem = sObjectType.newSObject();
for (SObject calcItem : calcItems) {
SObject oldCalcItem = oldCalcItems.get(calcItem.Id);
SObject oldCalcItem = isDelete ? possibleOldItem : oldCalcItems.get(calcItem.Id);
if (oldCalcItem != null) {
String currentLookup = (String) calcItem.get(fieldToken);
String oldLookup = (String) oldCalcItem.get(fieldToken);
if (currentLookup != oldLookup) {
wrapper.addRecordId(currentLookup);
if (isDelete) {
oldLookup = currentLookup;
currentLookup = null;
}
if (oldLookup != null) {
wrapper.addRecordId(oldLookup);
Set<String> oldIntermediateParents = wrapper.typeToOldIntermediateParents.get(calcItem.getSObjectType());
Set<Id> oldIntermediateParents = wrapper.typeToOldIntermediateParents.get(calcItem.getSObjectType());
if (oldIntermediateParents == null) {
oldIntermediateParents = new Set<String>();
oldIntermediateParents = new Set<Id>();
wrapper.typeToOldIntermediateParents.put(calcItem.getSObjectType(), oldIntermediateParents);
}
oldIntermediateParents.add(oldLookup);
oldIntermediateParents.add((Id) oldLookup);
}
}
}
Expand All @@ -2546,7 +2561,7 @@ global without sharing virtual class Rollup implements RollupLogger.ToStringObje
private String query;
private final List<SObject> parentRecordsToReset = new List<SObject>();
private final Set<String> recordIds = new Set<String>();
private final Map<Schema.SObjectType, Set<String>> typeToOldIntermediateParents = new Map<Schema.SObjectType, Set<String>>();
private final Map<Schema.SObjectType, Set<Id>> typeToOldIntermediateParents = new Map<Schema.SObjectType, Set<Id>>();

private QueryWrapper() {
}
Expand Down
2 changes: 1 addition & 1 deletion rollup/core/classes/RollupAsyncProcessor.cls
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,6 @@ global virtual without sharing class RollupAsyncProcessor extends Rollup impleme
this.stackDepth = innerRollup.stackDepth;
this.calcItemReplacer = innerRollup.calcItemReplacer;
this.lookupItems = innerRollup.lookupItems;
this.previouslyResetParents.addAll(innerRollup.previouslyResetParents);
}

// Inner rollup constructor - a rollup solely concerned with calculations
Expand Down Expand Up @@ -856,6 +855,7 @@ global virtual without sharing class RollupAsyncProcessor extends Rollup impleme
roll.oldCalcItems
)
.setIsFullRecalc(this.isFullRecalc)
.setOldIntermediateGrandparents(this.fullRecalcProcessor?.getOldIntermediateGrandparents())
.getParents(roll.calcItems);
} else if (roll.traversal?.getIsFinished() == false) {
roll.traversal.recommence();
Expand Down
4 changes: 2 additions & 2 deletions rollup/core/classes/RollupDateLiteral.cls
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ public without sharing abstract class RollupDateLiteral {
private class ThisWeekLiteral extends RollupDateLiteral {
public ThisWeekLiteral() {
this.ref = getRelativeDatetime(System.today().toStartOfWeek(), START_TIME);
this.bound = getRelativeDatetime(this.ref.addDays(6).date(), END_TIME);
this.bound = getRelativeDatetime(this.ref.addDays(6).dateGmt(), END_TIME);
}
}

Expand Down Expand Up @@ -392,7 +392,7 @@ public without sharing abstract class RollupDateLiteral {
private class ThisMonthLiteral extends RollupDateLiteral {
public ThisMonthLiteral() {
this.ref = getRelativeDatetime(System.today().toStartOfMonth(), START_TIME);
this.bound = offsetToFirstDay(getRelativeDatetime(this.ref.addMonths(1).date().toStartOfMonth(), END_TIME));
this.bound = offsetToFirstDay(getRelativeDatetime(this.ref.addMonths(1).dateGmt().toStartOfMonth(), END_TIME));
}
}

Expand Down
3 changes: 2 additions & 1 deletion rollup/core/classes/RollupEvaluator.cls
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,7 @@ public without sharing abstract class RollupEvaluator implements Rollup.Evaluato
if (this.hasValues == null) {
this.hasValues = true;
}
val = val.trim();
// coerce Boolean values to their standard representation
if (val.equalsIgnoreCase(TRUE_VAL)) {
val = TRUE_VAL;
Expand All @@ -556,7 +557,7 @@ public without sharing abstract class RollupEvaluator implements Rollup.Evaluato
} else if (val.equalsIgnoreCase('null')) {
val = null;
}
this.values.add(val?.trim());
this.values.add(val);
}
}
if (this.hasValues == null) {
Expand Down
18 changes: 16 additions & 2 deletions rollup/core/classes/RollupFullRecalcProcessor.cls
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
global abstract without sharing class RollupFullRecalcProcessor extends RollupAsyncProcessor.QueueableProcessor {
protected final List<Rollup__mdt> rollupMetas;
protected final Set<String> objIds = new Set<String>();
protected String queryString;

private final RollupFullRecalcProcessor postProcessor;
private final Map<Id, SObject> parentRecordsToClear = new Map<Id, SObject>();
private final List<RollupFullRecalcProcessor> cabooses = new List<RollupFullRecalcProcessor>();

protected String queryString;
private Map<Schema.SObjectType, Set<Id>> typeToOldIntermediateGrandparents;
private Boolean hasProcessedParentRecords = false;

protected RollupFullRecalcProcessor(
Expand Down Expand Up @@ -118,11 +119,24 @@ global abstract without sharing class RollupFullRecalcProcessor extends RollupAs
this.parentRecordsToClear.clear();
}

public void setOldIntermediateGrandparents(Map<Schema.SObjectType, Set<Id>> typeToOldIntermediateGrandparents) {
this.typeToOldIntermediateGrandparents = typeToOldIntermediateGrandparents;
}

public Map<Schema.SObjectType, Set<Id>> getOldIntermediateGrandparents() {
return this.typeToOldIntermediateGrandparents == null ? new Map<Schema.SObjectType, Set<Id>>() : this.typeToOldIntermediateGrandparents;
}

protected override RollupRepository preStart() {
return new RollupRepository(this.accessLevel).setArg(this.objIds).setArg('recordIds', this.recordIds).setQuery(this.queryString);
}

protected List<RollupAsyncProcessor> getDelegatedFullRecalcRollups(List<SObject> calcItems) {
for (Schema.SObjectType intermediateGrandparent : this.getOldIntermediateGrandparents().keySet()) {
for (Id resetId : this.getOldIntermediateGrandparents().get(intermediateGrandparent)) {
this.parentRecordsToClear.put(resetId, resetId.getSobjectType().newSObject(resetId));
}
}
RollupAsyncProcessor processor = this.getAsyncRollup(this.rollupMetas, this.calcItemType, calcItems, new Map<Id, SObject>(), null, this.invokePoint);
for (RollupAsyncProcessor innerRoll : processor.rollups) {
innerRoll.fullRecalcProcessor = this;
Expand Down
2 changes: 1 addition & 1 deletion rollup/core/classes/RollupLogger.cls
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
global without sharing virtual class RollupLogger implements ILogger {
@TestVisible
// this gets updated via the pipeline as the version number gets incremented
private static final String CURRENT_VERSION_NUMBER = 'v1.6.1';
private static final String CURRENT_VERSION_NUMBER = 'v1.6.2';
private static final LoggingLevel FALLBACK_LOGGING_LEVEL = LoggingLevel.DEBUG;
private static final RollupPlugin PLUGIN = new RollupPlugin();

Expand Down
Loading

0 comments on commit 86a0651

Please sign in to comment.