Skip to content

Commit

Permalink
v1.6.29 - Grandparent rollup full recalc bugfixes (#601)
Browse files Browse the repository at this point in the history
* Fixes an issue with retrieving all parents in one go for full recalc grandparent rollups when polymorphic fields are in play

* Fixes another issue found while investigating grandparent full recalcs, where recalcs started through the full recalc app would sometimes use only a cached subset of the children in each queueable/batch chunk
  • Loading branch information
jamessimone committed Jun 21, 2024
1 parent a2770f3 commit 274d640
Show file tree
Hide file tree
Showing 14 changed files with 133 additions and 50 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ sfge-*.log.gz
.pmdCache
#OSX
.DS_Store
.config
.config
.vscode
5 changes: 0 additions & 5 deletions .vscode/settings.json

This file was deleted.

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=04t6g000008ObN8AAK">
<a href="https://login.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008ObNNAA0">
<img alt="Deploy to Salesforce"
src="./media/deploy-package-to-prod.png">
</a>

<a href="https://test.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008ObN8AAK">
<a href="https://test.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008ObNNAA0">
<img alt="Deploy to Salesforce Sandbox"
src="./media/deploy-package-to-sandbox.png">
</a>
Expand Down
45 changes: 45 additions & 0 deletions extra-tests/classes/RollupFullRecalcTests.cls
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,51 @@ private class RollupFullRecalcTests {
System.assertEquals(events[0].Subject + ', ' + events[1].Subject, updatedUser.AboutMe, 'Grandparent rollup should have worked!');
}

@IsTest
static void shouldMapChildrenWithDifferentGrandparentWhereClauses() {
Account acc = [SELECT Id, OwnerId FROM Account];
List<Event> events = new List<Event>{
new Event(WhatId = acc.Id, Subject = 'Two', ActivityDatetime = System.now(), DurationInMinutes = 30),
new Event(WhatId = acc.Id, Subject = 'One', ActivityDatetime = System.now(), DurationInMinutes = 60)
};
insert events;

Rollup.defaultControl = Rollup.getDefaultControl();
Rollup.defaultControl.ShouldSkipResettingParentFields__c = true;
Rollup.rollupMetadata = new List<Rollup__mdt>{
new Rollup__mdt(
CalcItem__c = 'Event',
RollupFieldOnCalcItem__c = 'Subject',
LookupFieldOnCalcItem__c = 'WhatId',
LookupObject__c = 'User',
LookupFieldOnLookupObject__c = 'Id',
RollupFieldOnLookupObject__c = 'AboutMe',
RollupOperation__c = 'CONCAT',
GrandparentRelationshipFieldPath__c = 'What.Owner.AboutMe',
CalcItemWhereClause__c = 'DurationInMinutes <= 30'
),
new Rollup__mdt(
CalcItem__c = 'Event',
RollupFieldOnCalcItem__c = 'Subject',
LookupFieldOnCalcItem__c = 'WhatId',
LookupObject__c = 'User',
LookupFieldOnLookupObject__c = 'Id',
RollupFieldOnLookupObject__c = 'CompanyName',
RollupOperation__c = 'CONCAT',
GrandparentRelationshipFieldPath__c = 'What.Owner.CompanyName',
CalcItemWhereClause__c = 'DurationInMinutes > 30'
)
};

Test.startTest();
Rollup.performBulkFullRecalc(Rollup.rollupMetadata, Rollup.InvocationPoint.FROM_FULL_RECALC_LWC.name());
Test.stopTest();

User updatedUser = [SELECT AboutMe, CompanyName FROM User WHERE Id = :acc.OwnerId];
System.assertEquals(events[0].Subject, updatedUser.AboutMe);
System.assertEquals(events[1].Subject, updatedUser.CompanyName);
}

@IsTest
static void shouldAllowGrandparentRollupFromParentWithPolymorphicFields() {
// technically shouldAllowGrandparentRollupsFromParent also tests polymorphic fields
Expand Down
23 changes: 23 additions & 0 deletions extra-tests/classes/RollupRelationshipFieldFinderTests.cls
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,29 @@ private class RollupRelationshipFieldFinderTests {
System.assertEquals(true, traversal.getIsFinished());
}

@IsTest
static void fullRecalcDoesNotBlowUpOnDynamicParentFields() {
Account acc = [SELECT Id, OwnerId, Owner.Name FROM Account];

ContactPointAddress child = new ContactPointAddress(ParentId = acc.Id, Name = 'Child');
insert child;

RollupRelationshipFieldFinder finder = new RollupRelationshipFieldFinder(
control,
new Rollup__mdt(GrandparentRelationshipFieldPath__c = 'Parent.Owner.Name'),
new Set<String>{ 'ParentId, Name' },
uniqueFieldNames,
User.SObjectType,
new Map<Id, SObject>()
);
finder.setIsFullRecalc(true);
RollupRelationshipFieldFinder.Traversal traversal = finder.getParents(new List<ContactPointAddress>{ child });

System.assertEquals(true, traversal.getParentLookupToRecords().containsKey(acc.OwnerId));
System.assertEquals(acc.Owner.Name, traversal.retrieveParents(child.Id)[0].get('Name'));
System.assertEquals(true, traversal.getIsFinished());
}

@IsTest
static void shouldNotReportFalsePositiveIfUltimateParentStaysTheSame() {
Account intermediateOne = new Account(Name = 'Intermediate 1');
Expand Down
3 changes: 0 additions & 3 deletions extra-tests/classes/RollupTests.cls
Original file line number Diff line number Diff line change
Expand Up @@ -2899,9 +2899,6 @@ private class RollupTests {
insert cpas;

RollupTestUtils.DMLMock mock = RollupTestUtils.loadMock(cpas);
Rollup.defaultControl = new RollupControl__mdt(MaxNumberOfQueries__c = 2, MaxRollupRetries__c = 100);
// start as synchronous rollup to allow for one deferral
Rollup.specificControl = new RollupControl__mdt(ShouldRunAs__c = RollupMetaPicklists.ShouldRunAs.Synchronous, MaxNumberOfQueries__c = 2);
Rollup.rollupMetadata = new List<Rollup__mdt>{
new Rollup__mdt(
CalcItem__c = 'ContactPointAddress',
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "apex-rollup",
"version": "1.6.28",
"version": "1.6.29",
"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 Expand Up @@ -58,4 +58,4 @@
"test:apex": "sh ./scripts/runLocalTests.sh",
"test:lwc": "sfdx-lwc-jest --coverage --skipApiVersionCheck"
}
}
}
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=04t6g000008ObNDAA0">
<a href="https://login.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008ObNSAA0">
<img alt="Deploy to Salesforce"
src="./media/deploy-package-to-prod.png">
</a>

<a href="https://test.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008ObNDAA0">
<a href="https://test.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008ObNSAA0">
<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": "Summer 24 release",
"versionNumber": "1.1.22.0",
"versionName": "Grandparent full recalc bugfixes",
"versionNumber": "1.1.23.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.1.19": "04t6g000008ObCsAAK",
"apex-rollup-namespaced@1.1.20": "04t6g000008ObCxAAK",
"apex-rollup-namespaced@1.1.21": "04t6g000008ObLCAA0",
"apex-rollup-namespaced@1.1.22": "04t6g000008ObNDAA0"
"apex-rollup-namespaced@1.1.22": "04t6g000008ObNDAA0",
"apex-rollup-namespaced@1.1.23": "04t6g000008ObNSAA0"
}
}
25 changes: 12 additions & 13 deletions rollup/core/classes/RollupAsyncProcessor.cls
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ global virtual without sharing class RollupAsyncProcessor extends Rollup impleme
RollupRepository repo = new RollupRepository(this.runAsMode);
if (this.rollups.isEmpty() == false) {
RollupAsyncProcessor firstRollup = this.rollups.get(0);
repo.setArg(this.getCalcItemsByLookupField(firstRollup).keySet());
repo.setArg(this.getCalcItemsByLookupField(firstRollup, null).keySet());
query = RollupQueryBuilder.Current.getQuery(
firstRollup.lookupObj,
new List<String>(this.lookupObjectToUniqueFieldNames.get(firstRollup.lookupObj)),
Expand Down Expand Up @@ -713,12 +713,12 @@ global virtual without sharing class RollupAsyncProcessor extends Rollup impleme
continue;
}

String grandparentKey = '' + roll.lookupObj + roll.calcItemType + (roll.metadata.CalcItemWhereClause__c ?? '');
if (String.isNotBlank(roll.metadata.GrandparentRelationshipFieldPath__c) && grandparentRollups.containsKey(grandparentKey)) {
String grandparentKey = '' + roll.lookupObj + roll.calcItemType + (roll.metadata.CalcItemWhereClause__c?.trim() ?? '');
if (String.isNotBlank(roll.metadata.GrandparentRelationshipFieldPath__c) && grandparentRollups.containsKey(grandparentKey) && roll.traversal == null) {
roll.traversal = grandparentRollups.get(grandparentKey);
}

Map<String, CalcItemBag> calcItemsByLookupField = this.getCalcItemsByLookupField(roll);
Map<String, CalcItemBag> calcItemsByLookupField = this.getCalcItemsByLookupField(roll, grandparentKey);
// some rollups may not finish retrieving all parent rows the first time around - and that's ok! we can keep
// trying until all necessary records have been retrieved
if (roll.traversal?.getIsFinished() == false) {
Expand Down Expand Up @@ -945,12 +945,12 @@ global virtual without sharing class RollupAsyncProcessor extends Rollup impleme
rolls.sort();
}

private Map<String, CalcItemBag> getCalcItemsByLookupField(RollupAsyncProcessor roll) {
private Map<String, CalcItemBag> getCalcItemsByLookupField(RollupAsyncProcessor roll, String cacheKey) {
Map<String, CalcItemBag> lookupFieldToCalcItems = new Map<String, CalcItemBag>();
if (roll.calcItems == null) {
return new Map<String, CalcItemBag>();
return lookupFieldToCalcItems;
}

String cacheKey = '' + roll.lookupObj + roll.lookupFieldOnCalcItem;
switch on this.invokePoint {
when FROM_FULL_RECALC_LWC, FROM_SINGULAR_PARENT_RECALC_LWC, FROM_FULL_RECALC_FLOW {
Map<String, CalcItemBag> possiblyCachedItems = CACHED_CALC_ITEM_BAGS.get(cacheKey);
Expand All @@ -960,10 +960,9 @@ global virtual without sharing class RollupAsyncProcessor extends Rollup impleme
}
}

Map<String, CalcItemBag> lookupFieldToCalcItems = new Map<String, CalcItemBag>();
if (String.isNotBlank(roll.metadata.GrandparentRelationshipFieldPath__c) || roll.metadata.RollupToUltimateParent__c) {
if (roll.traversal == null) {
roll.traversal = new RollupRelationshipFieldFinder(
roll.traversal = roll.traversal ??
new RollupRelationshipFieldFinder(
roll.rollupControl,
roll.metadata,
this.calcObjectToUniqueFieldNames.get(roll.calcItemType),
Expand All @@ -974,11 +973,11 @@ global virtual without sharing class RollupAsyncProcessor extends Rollup impleme
.setIsFullRecalc(this.isFullRecalc)
.setOldIntermediateGrandparents(this.fullRecalcProcessor?.getOldIntermediateGrandparents())
.getParents(roll.calcItems);
} else if (roll.traversal?.getIsFinished() == false) {
roll.traversal.recommence();
}

if (roll.traversal.getIsFinished()) {
lookupFieldToCalcItems = roll.traversal.getParentLookupToRecords();
} else {
roll.traversal.recommence();
}
} else {
for (SObject calcItem : roll.calcItems) {
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.28';
private static final String CURRENT_VERSION_NUMBER = 'v1.6.29';
private static final System.LoggingLevel FALLBACK_LOGGING_LEVEL = System.LoggingLevel.DEBUG;
private static final RollupPlugin PLUGIN = new RollupPlugin();

Expand Down
7 changes: 7 additions & 0 deletions rollup/core/classes/RollupParentResetProcessor.cls
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ public without sharing class RollupParentResetProcessor extends RollupFullBatchR
return this.rollupControl.ShouldSkipResettingParentFields__c ? this.getNoProcessId() : System.enqueueJob(new QueueableResetProcessor(this));
}

protected override Map<String, String> customizeToStringEntries(Map<String, String> props) {
props = super.customizeToStringEntries(props);
props.remove('Rollup Control');
props.remove('Rollup Metadata');
return props;
}

private void runSync() {
List<SObject> parentItems = this.preStart().get();
this.execute(null, parentItems);
Expand Down
44 changes: 29 additions & 15 deletions rollup/core/classes/RollupRelationshipFieldFinder.cls
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,16 @@ public without sharing class RollupRelationshipFieldFinder {
}

private void getFullRecalcParents(List<SObject> children) {
try {
this.safelyRetrieveFullRecalcParents(children);
} catch (Exception ex) {
RollupLogger.Instance.log('Error retrieving parents all in one go: ' + ex.getMessage() + ' falling back to standard approach', System.LoggingLevel.DEBUG);
this.isFullRecalc = false;
this.getParents(children);
}
}

private void safelyRetrieveFullRecalcParents(List<SObject> children) {
this.setFirstRunFlags(children);
Set<String> parentFieldNames = new Set<String>{ this.metadata.GrandparentRelationshipFieldPath__c };
String baseGrandparentPath = this.metadata.GrandparentRelationshipFieldPath__c.substringBeforeLast('.');
Expand Down Expand Up @@ -829,6 +839,10 @@ public without sharing class RollupRelationshipFieldFinder {
}
this.prepFinishedObject(finalParents);
}
this.handleIntermediateGrandparents(splitRelationshipNames);
}

private void handleIntermediateGrandparents(List<String> splitRelationshipNames) {
for (Schema.SObjectType intermediateGrandparent : this.typeToOldIntermediateGrandparents.keySet()) {
List<Schema.SObjectField> fields = intermediateGrandparent.getDescribe().fields.getMap().values();
Boolean hasFoundMatch = false;
Expand All @@ -846,23 +860,23 @@ public without sharing class RollupRelationshipFieldFinder {
Schema.SObjectType idType = iterator.hasNext() ? iterator.next().getSObjectType() : null;
// TODO handle idType == null
if (idType != this.ultimateParent) {
continue;
continue;
}
fullParentPath = splitRelationshipName + this.metadata.GrandparentRelationshipFieldPath__c.substringAfter(splitRelationshipName);
String query = RollupQueryBuilder.Current.getQuery(
this.ultimateParent,
new List<String>{ this.metadata.RollupFieldOnLookupObject__c, this.metadata.LookupFieldOnLookupObject__c },
'Id',
'=',
this.metadata.CalcItemWhereClause__c
);

List<SObject> otherPossibleGrandparents = this.repo.setQuery(query).setArg(otherIds).get();
for (SObject otherPossibleGrandparent : otherPossibleGrandparents) {
if (this.traversal.finalParentIdToRecords.containsKey(otherPossibleGrandparent.Id) == false) {
this.traversal.oldGrandparents.add(otherPossibleGrandparent);
}
fullParentPath = splitRelationshipName + this.metadata.GrandparentRelationshipFieldPath__c.substringAfter(splitRelationshipName);
String query = RollupQueryBuilder.Current.getQuery(
this.ultimateParent,
new List<String>{ this.metadata.RollupFieldOnLookupObject__c, this.metadata.LookupFieldOnLookupObject__c },
'Id',
'=',
this.metadata.CalcItemWhereClause__c
);

List<SObject> otherPossibleGrandparents = this.repo.setQuery(query).setArg(otherIds).get();
for (SObject otherPossibleGrandparent : otherPossibleGrandparents) {
if (this.traversal.finalParentIdToRecords.containsKey(otherPossibleGrandparent.Id) == false) {
this.traversal.oldGrandparents.add(otherPossibleGrandparent);
}
}
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions sfdx-project.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"package": "apex-rollup",
"path": "rollup",
"scopeProfiles": true,
"versionName": "Summer 24 release",
"versionNumber": "1.6.28.0",
"versionName": "Grandparent full recalc bugfixes",
"versionNumber": "1.6.29.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 Down Expand Up @@ -106,6 +106,7 @@
"apex-rollup@1.6.25": "04t6g000008Ob22AAC",
"apex-rollup@1.6.26": "04t6g000008ObCnAAK",
"apex-rollup@1.6.27": "04t6g000008ObL7AAK",
"apex-rollup@1.6.28": "04t6g000008ObN8AAK"
"apex-rollup@1.6.28": "04t6g000008ObN8AAK",
"apex-rollup@1.6.29": "04t6g000008ObNNAA0"
}
}

0 comments on commit 274d640

Please sign in to comment.