Skip to content

Commit 26229a5

Browse files
authored
V1.3.1 - Heap Size Improvements, Deferred Rollup Bugfix (#163)
* Doing some cleanup on the v1.3.0 release - making the pipeline more resilient by hardening the scripts/build-and-promote-package.ps1 ability to find the prior package version, removing some duplication between deploy.yml and package.json npm scripts, and removed this.syncRollups property from RollupAsyncProcessor to cut down on heap size, made runCalc logic easier to follow * Less of a hack to get the key value for the last rollup package, thanks to @gdoenlen for the assist on this one * Fix deferred rollup issue where a queueable with multiple targets turns into a batch job without regard for the batch job's lookup items not all matching the inner rollups lookup SObjectType (and field references, accordingly) * Bumping package version from Github Action
1 parent 275cd3d commit 26229a5

File tree

7 files changed

+114
-35
lines changed

7 files changed

+114
-35
lines changed

.github/workflows/deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ jobs:
7373
- name: Install & run SFDX Scanner
7474
run: |
7575
sfdx plugins:install @salesforce/sfdx-scanner
76-
sfdx scanner:run --pmdconfig config/pmd-ruleset.xml --target . --engine pmd --severity-threshold 3
76+
npm run scan
7777
7878
# Store secret for dev hub
7979
- name: 'Populate auth file with DEVHUB_SFDX_URL secret'

extra-tests/classes/RollupIntegrationTests.cls

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,7 +1403,7 @@ private class RollupIntegrationTests {
14031403
Rollup.records = cpas;
14041404
Rollup.shouldRun = true;
14051405
Rollup.apexContext = TriggerOperation.AFTER_INSERT;
1406-
Rollup.specificControl = new RollupControl__mdt(ShouldRunAs__c = 'Synchronous Rollup');
1406+
Rollup.specificControl = new RollupControl__mdt(ShouldRunAs__c = RollupMetaPicklists.ShouldRunAs.Synchronous);
14071407

14081408
// specifically do NOT wrap in Test.startTest() / Test.stopTest() - we need to ensure this happened synchronously
14091409
Rollup.countFromApex(ContactPointAddress.PreferenceRank, ContactPointAddress.ParentId, Account.Id, Account.AnnualRevenue, Account.SObjectType).runCalc();
@@ -1414,7 +1414,6 @@ private class RollupIntegrationTests {
14141414

14151415
@IsTest
14161416
static void shouldPartiallyDeferRollupCalculationWhenOverLimits() {
1417-
Rollup.specificControl = new RollupControl__mdt(ShouldAbortRun__c = true);
14181417
Account acc = [SELECT Id, OwnerId FROM Account];
14191418
Account secondParent = new Account(Name = 'Second parent');
14201419
insert secondParent;
@@ -1425,10 +1424,14 @@ private class RollupIntegrationTests {
14251424
};
14261425
insert cpas;
14271426

1428-
Rollup.defaultControl = new RollupControl__mdt(BatchChunkSize__c = 1, MaxRollupRetries__c = 1, MaxNumberOfQueries__c = 2, IsRollupLoggingEnabled__c = true);
14291427
// start as synchronous rollup to allow for one deferral
1430-
Rollup.specificControl = new RollupControl__mdt(ShouldRunAs__c = 'Synchronous Rollup');
1431-
1428+
Rollup.defaultControl = new RollupControl__mdt(
1429+
BatchChunkSize__c = 1,
1430+
MaxRollupRetries__c = 1,
1431+
MaxNumberOfQueries__c = 2,
1432+
IsRollupLoggingEnabled__c = true,
1433+
ShouldRunAs__c = RollupMetaPicklists.ShouldRunAs.Synchronous
1434+
);
14321435
Rollup.shouldRun = true;
14331436
Rollup.records = cpas;
14341437
Rollup.rollupMetadata = new List<Rollup__mdt>{
@@ -1460,6 +1463,71 @@ private class RollupIntegrationTests {
14601463
}
14611464
}
14621465

1466+
@IsTest
1467+
static void shouldNotBlowUpWhenDeferralLeadsToMultipleSObjectsBeingPresent() {
1468+
Account acc = [SELECT Id, OwnerId FROM Account];
1469+
1470+
Individual indy = new Individual(LastName = 'Indy');
1471+
insert indy;
1472+
1473+
List<ContactPointAddress> cpas = new List<ContactPointAddress>{
1474+
new ContactPointAddress(ParentId = acc.Id, Name = 'One', PreferenceRank = 1),
1475+
new ContactPointAddress(ParentId = indy.Id, Name = 'Two', PreferenceRank = 1)
1476+
};
1477+
insert cpas;
1478+
1479+
Rollup.defaultControl = new RollupControl__mdt(
1480+
BatchChunkSize__c = 2,
1481+
MaxRollupRetries__c = 1,
1482+
MaxNumberOfQueries__c = 2,
1483+
IsRollupLoggingEnabled__c = true,
1484+
ShouldRunAs__c = RollupMetaPicklists.ShouldRunAs.Synchronous
1485+
);
1486+
RollupAsyncProcessor.shouldRunAsBatch = true;
1487+
Rollup.shouldRun = true;
1488+
Rollup.records = cpas;
1489+
Rollup.rollupMetadata = new List<Rollup__mdt>{
1490+
new Rollup__mdt(
1491+
CalcItem__c = 'ContactPointAddress',
1492+
RollupFieldOnCalcItem__c = 'PreferenceRank',
1493+
LookupFieldOnCalcItem__c = 'ParentId',
1494+
LookupObject__c = 'Account',
1495+
LookupFieldOnLookupObject__c = 'Id',
1496+
RollupFieldOnLookupObject__c = 'AnnualRevenue',
1497+
RollupOperation__c = 'AVERAGE'
1498+
),
1499+
new Rollup__mdt(
1500+
CalcItem__c = 'ContactPointAddress',
1501+
RollupFieldOnCalcItem__c = 'PreferenceRank',
1502+
LookupFieldOnCalcItem__c = 'ParentId',
1503+
LookupObject__c = 'Individual',
1504+
LookupFieldOnLookupObject__c = 'Id',
1505+
RollupFieldOnLookupObject__c = 'ChildrenCount',
1506+
RollupOperation__c = 'COUNT'
1507+
),
1508+
new Rollup__mdt(
1509+
CalcItem__c = 'ContactPointAddress',
1510+
RollupFieldOnCalcItem__c = 'Name',
1511+
LookupFieldOnCalcItem__c = 'ParentId',
1512+
LookupObject__c = 'Individual',
1513+
LookupFieldOnLookupObject__c = 'Id',
1514+
RollupFieldOnLookupObject__c = 'LastName',
1515+
RollupOperation__c = 'CONCAT'
1516+
)
1517+
};
1518+
Rollup.apexContext = TriggerOperation.AFTER_INSERT;
1519+
1520+
Test.startTest();
1521+
Rollup.runFromTrigger();
1522+
Test.stopTest();
1523+
1524+
acc = [SELECT AnnualRevenue FROM Account];
1525+
indy = [SELECT LastName, ChildrenCount FROM Individual];
1526+
System.assertEquals(1, acc.AnnualRevenue, 'Annual revenue should have been averaged properly');
1527+
System.assertEquals(1, indy.ChildrenCount, 'Children count should have been counted correctly');
1528+
System.assertEquals('Indy, Two', indy.LastName, 'LastName should have been concat properly');
1529+
}
1530+
14631531
@IsTest
14641532
static void shouldRunDirectlyFromApex() {
14651533
Account acc = [SELECT Id FROM Account];

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "apex-rollup",
3-
"version": "1.3.0",
3+
"version": "1.3.1",
44
"description": "Fast, configurable, elastically scaling custom rollup solution. Apex Invocable action, one-liner Apex trigger/CMDT-driven logic, and scheduled Apex-ready.",
55
"repository": {
66
"type": "git",

rollup/core/classes/Rollup.cls

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -542,16 +542,15 @@ global without sharing virtual class Rollup {
542542
if (flowInput.recordsToRollup?.isEmpty() != false) {
543543
flowOutput.message = 'No records to rollup, returning early';
544544
} else {
545-
SObject firstRecord = flowInput.recordsToRollup[0];
546545
// flow collections are not strongly typed, so we grab from the first record
547-
SObjectType sObjectType = firstRecord.getSObjectType();
546+
SObjectType sObjectType = flowInput.recordsToRollup[0].getSObjectType();
548547
// this will throw back up to the Flow engine if the inputs don't pass validation
549548
enforceValidationRules(flowInput);
550549
enforceFlowSpecificRules(flowInput, sObjectType);
551550
winnowOldFlowRecords(flowInput);
552551

553552
Map<Id, SObject> oldFlowRecords = new Map<Id, SObject>(flowInput.oldRecordsToRollup);
554-
String rollupContext = getFlowRollupContext(flowInput, firstRecord, oldFlowRecords);
553+
String rollupContext = getFlowRollupContext(flowInput, flowInput.recordsToRollup[0], sObjectType, oldFlowRecords);
555554

556555
Rollup__mdt meta = transformFlowInputToRollupMetadata(flowInput, rollupContext, sObjectType);
557556
if (metaToInput.containsKey(meta)) {
@@ -2048,7 +2047,7 @@ global without sharing virtual class Rollup {
20482047
}
20492048
}
20502049

2051-
private static String getFlowRollupContext(FlowInput firstInput, SObject firstRecord, Map<Id, SObject> oldFlowRecords) {
2050+
private static String getFlowRollupContext(FlowInput firstInput, SObject firstRecord, SObjectType sObjectType, Map<Id, SObject> oldFlowRecords) {
20522051
String flowContext = firstInput.rollupContext.toUpperCase();
20532052
if (String.isBlank(flowContext) || flowContext == 'UPSERT' && oldFlowRecords.containsKey(firstRecord.Id) == false) {
20542053
flowContext = 'INSERT';
@@ -2068,7 +2067,7 @@ global without sharing virtual class Rollup {
20682067
matchingOperation = TriggerOperation.BEFORE_DELETE;
20692068
}
20702069
}
2071-
populateCachedApexOperations(firstRecord.getSObjectType(), matchingOperation);
2070+
populateCachedApexOperations(sObjectType, matchingOperation);
20722071

20732072
return flowContext == 'INSERT' ? '' : flowContext + '_';
20742073
}

rollup/core/classes/RollupAsyncProcessor.cls

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ global virtual without sharing class RollupAsyncProcessor extends Rollup impleme
88
private final Op op;
99

1010
private final List<RollupAsyncProcessor> deferredRollups = new List<RollupAsyncProcessor>();
11-
private final List<RollupAsyncProcessor> syncRollups = new List<RollupAsyncProcessor>();
1211

1312
protected final SObjectType calcItemType;
1413
protected Boolean isProcessed = false;
@@ -21,7 +20,6 @@ global virtual without sharing class RollupAsyncProcessor extends Rollup impleme
2120
private List<SObject> lookupItems;
2221
private Map<String, List<SObject>> cachedFullRecalcs;
2322

24-
private static final RollupSettings__c SETTINGS = RollupSettings__c.getInstance();
2523
private static Integer stackDepth = 0;
2624
private static Boolean isRunningAsync = false;
2725
@TestVisible
@@ -239,9 +237,16 @@ global virtual without sharing class RollupAsyncProcessor extends Rollup impleme
239237
}
240238

241239
public virtual void execute(Database.BatchableContext context, List<SObject> lookupItems) {
242-
for (RollupAsyncProcessor rollup : this.rollups) {
243-
this.initializeRollupFieldDefaults(lookupItems, rollup);
240+
for (Integer index = this.rollups.size() - 1; index >= 0; index--) {
241+
RollupAsyncProcessor roll = this.rollups[index];
242+
if (lookupItems.getSObjectType() != roll.lookupObj) {
243+
this.rollups.remove(index);
244+
this.deferredRollups.add(roll);
245+
} else {
246+
this.initializeRollupFieldDefaults(lookupItems, roll);
247+
}
244248
}
249+
245250
this.lookupItems = lookupItems;
246251
this.process(this.rollups);
247252
RollupLogger.Instance.save();
@@ -253,23 +258,20 @@ global virtual without sharing class RollupAsyncProcessor extends Rollup impleme
253258

254259
public virtual override String runCalc() {
255260
// side effect in the below method - rollups can be removed from this.rollups if a control record ShouldAbortRun__c == true
256-
// they also can be added to this.syncRollups if we're already async
257-
this.ingestRollupControlData();
261+
// they also can be added to syncRollups if we're already async
262+
List<RollupAsyncProcessor> syncRollups = new List<RollupAsyncProcessor>();
263+
this.ingestRollupControlData(syncRollups);
258264

259265
if (this.isNoOp) {
260-
this.isNoOp = this.rollups.isEmpty() && this.syncRollups.isEmpty() && this.calcItems?.isEmpty() == true;
266+
this.isNoOp = this.rollups.isEmpty() && syncRollups.isEmpty() && this.calcItems?.isEmpty() == true;
261267
}
262268

263269
String rollupProcessId = 'No process Id';
264-
if (this.isNoOp || this.rollupControl.ShouldAbortRun__c || SETTINGS.IsEnabled__c == false) {
270+
if (this.isNoOp || this.rollupControl.ShouldAbortRun__c || RollupSettings__c.getInstance().IsEnabled__c == false) {
265271
RollupLogger.Instance.log('no-op, exiting early to avoid burning async job', LoggingLevel.FINE);
266-
RollupLogger.Instance.save();
267-
return rollupProcessId;
268-
}
269-
270-
if (this.syncRollups.isEmpty() == false) {
272+
} else if (syncRollups.isEmpty() == false) {
271273
RollupLogger.Instance.log('about to process sync rollups', LoggingLevel.DEBUG);
272-
this.process(this.syncRollups);
274+
this.process(syncRollups);
273275
RollupLogger.Instance.log('finished running sync rollups', LoggingLevel.DEBUG);
274276
rollupProcessId = 'running rollups flagged to go synchronously';
275277
} else {
@@ -781,7 +783,7 @@ global virtual without sharing class RollupAsyncProcessor extends Rollup impleme
781783
return isValid;
782784
}
783785

784-
private void ingestRollupControlData() {
786+
private void ingestRollupControlData(List<RollupAsyncProcessor> syncRollups) {
785787
RollupControl__mdt orgDefaults = this.rollupControl;
786788
for (Integer index = this.rollups.size() - 1; index >= 0; index--) {
787789
RollupAsyncProcessor rollup = this.rollups[index];
@@ -799,7 +801,7 @@ global virtual without sharing class RollupAsyncProcessor extends Rollup impleme
799801
this.rollups.remove(index);
800802
} else if (couldRunSync && shouldRunSyncDeferred == false) {
801803
this.rollups.remove(index);
802-
this.syncRollups.add(rollup);
804+
syncRollups.add(rollup);
803805
} else if (couldRunSync && shouldRunSyncDeferred) {
804806
this.rollups.remove(index);
805807
this.getCachedRollups().add(rollup);

scripts/build-and-promote-package.ps1

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ function Get-Apex-Rollup-Package-Alias {
1313
param (
1414
$packageVersion
1515
)
16-
if ($packageVersion.EndsWith(".0")) {
17-
$packageVersion = $packageVersion.Substring(0, $packageVersion.length - 2);
16+
$matchString = ".0-0"
17+
if ($packageVersion.EndsWith($matchString)) {
18+
$packageVersion = $packageVersion.Substring(0, $packageVersion.Length - $matchString.Length);
1819
}
1920
return "apex-rollup@$packageVersion-0"
2021
}
@@ -71,12 +72,20 @@ try {
7172
$priorPackageVersionId = $sfdxProjectJson.packageAliases[0] | Select-Object -ExpandProperty (Get-Apex-Rollup-Package-Alias $priorPackageVersionNumber)
7273
} catch {
7374
# if there hasn't been a current version of the package, get the previous version and its associated package Id
74-
$currentPackageNumber = ([int]($currentPackageVersion | Select-String -Pattern \S\S\.0).Matches.Value)
75+
$currentPackageNumber = ([int]($currentPackageVersion | Select-String -Pattern '(\d|\d\d)\.0').Matches.Value)
7576
$currentPackageNumberString = $currentPackageNumber.ToString()
7677
$priorPackageVersionString = ($currentPackageNumber - 1).ToString()
7778

7879
$priorPackageVersionNumber = (Update-Last-Substring $currentPackageVersion $currentPackageNumberString $priorPackageVersionString)
79-
$priorPackageVersionId = $sfdxProjectJson.packageAliases[0] | Select-Object -ExpandProperty (Get-Apex-Rollup-Package-Alias $priorPackageVersionNumber)
80+
try {
81+
$priorPackageVersionId = $sfdxProjectJson.packageAliases[0] | Select-Object -ExpandProperty (Get-Apex-Rollup-Package-Alias $priorPackageVersionNumber)
82+
} catch {
83+
$allPackages = $sfdxProjectJson.packageAliases[0] | Select-Object -ExcludeProperty 'Nebula Logger*' | Get-Member -MemberType NoteProperty | Select-Object
84+
$priorPackageVersionFull = $allPackages[$allPackages.Length - 1].Name
85+
$apexRollupPosition = $priorPackageVersionFull.IndexOf("@")
86+
$priorPackageVersionId = $priorPackageVersionFull.Substring($apexRollupPosition + 1).Split('-')[0]
87+
$priorPackageVersionNumber = $priorPackageVersionId
88+
}
8089
}
8190

8291
Write-Output "Prior package version: $priorPackageVersionId"
@@ -120,7 +129,7 @@ if($currentBranch -eq "main") {
120129
if ($packageJson.version -ne $currentPackageVersion) {
121130
Write-Output "Bumping package.json version to: $currentPackageVersion"
122131

123-
$packageJson.version = $currentPackageVersion
132+
$packageJson.version = Update-Last-Substring $currentPackageVersion ".0" ""
124133
$packagePath = "./package.json"
125134
ConvertTo-Json -InputObject $packageJson | Set-Content -Path $packagePath -NoNewline
126135

sfdx-project.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"default": true,
55
"package": "apex-rollup",
66
"path": "rollup",
7-
"versionNumber": "1.3.0.0",
7+
"versionNumber": "1.3.1.0",
88
"versionDescription": "Fixes parent-level filtering for Flow, deprecated RollupLoggerPlugin__mdt in favor of RollupPlugin__mdt, adds PMD analysis",
99
"releaseNotesUrl": "https://github.com/jamessimone/apex-rollup/releases/latest",
1010
"unpackagedMetadata": {
@@ -86,6 +86,7 @@
8686
"[email protected]": "04t6g000008ShMNAA0",
8787
"[email protected]": "04t6g000008ShPMAA0",
8888
"[email protected]": "04t6g000008ShSaAAK",
89-
"[email protected]": "04t6g000008ShUCAA0"
89+
"[email protected]": "04t6g000008ShUCAA0",
90+
"[email protected]": "04t6g000008ShZ8AAK"
9091
}
9192
}

0 commit comments

Comments
 (0)