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

v1.5.74 - 2 Children Types Rolling Up To Same Parent Field #460

Merged
merged 6 commits into from
Jun 30, 2023
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
6 changes: 4 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=04t6g000008So7xAAC">
<a href="https://login.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008So9AAAS">
<img alt="Deploy to Salesforce"
src="./media/deploy-package-to-prod.png">
</a>

<a href="https://test.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008So7xAAC">
<a href="https://test.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008So9AAAS">
<img alt="Deploy to Salesforce Sandbox"
src="./media/deploy-package-to-sandbox.png">
</a>
Expand Down Expand Up @@ -161,6 +161,8 @@ Within the `Rollup__mdt` custom metadata type, add a new record with fields:

You can have as many rollups as you'd like per object/trigger — all operations are boxcarred together for optimal efficiency.

It is possible to have do SUM/COUNT-based rollups from different children types to the same parent field because these rollup operations utilize diff-based calculations by default. If you are looking to roll up values using other rollup operation types to the same parent field from different children, you may be able to do so with additional setup.

#### Special Considerations For Usage Of Child Object Where Clauses

In addition to the above, some other considerations when it comes to the where clause:
Expand Down
42 changes: 42 additions & 0 deletions extra-tests/classes/RollupFlowBulkProcessorTests.cls
Original file line number Diff line number Diff line change
Expand Up @@ -424,4 +424,46 @@ private class RollupFlowBulkProcessorTests {
Account acc = [SELECT AnnualRevenue, NumberOfEmployees FROM Account];
System.assertEquals(10, acc.AnnualRevenue, 'Account annual revenue should have ordered properly');
}

@IsTest
static void supportsOneToManyUpdates() {
Rollup.rollupMetadata = new List<Rollup__mdt>{
new Rollup__mdt(
CalcItem__c = 'Account',
LookupObject__c = 'Individual',
LookupFieldOnCalcItem__c = 'Id',
RollupOperation__c = 'MIN',
RollupFieldOnLookupObject__c = 'ConsumerCreditScore',
RollupFieldOnCalcItem__c = 'AnnualRevenue',
LookupFieldOnLookupObject__c = 'Id',
GrandparentRelationshipFieldPath__c = 'Contacts.Individual.ConsumerCreditScore',
OneToManyGrandparentFields__c = 'Contact.AccountId'
)
};
Rollup.onlyUseMockMetadata = true;
Account acc = [SELECT Id, AnnualRevenue, MasterRecordId FROM Account];
acc.AnnualRevenue = 67;
update acc;
Account secondChild = new Account(Name = 'new actual min', AnnualRevenue = 43);
Individual indy = new Individual(LastName = 'Indy');

insert new List<SObject>{ secondChild, indy };
insert new List<Contact>{
new Contact(LastName = 'One To Many Child', AccountId = acc.Id, IndividualId = indy.Id),
new Contact(LastName = 'Second one to many', AccountId = secondChild.Id, IndividualId = indy.Id)
};

RollupFlowBulkProcessor.FlowInput input = new RollupFlowBulkProcessor.FlowInput();
input.recordsToRollup = new List<SObject>{ acc };
input.rollupContext = 'UPSERT';
input.oldRecordsToRollup = new List<SObject>{ new Account(Id = acc.Id, AnnualRevenue = secondChild.AnnualRevenue - 1) };
input.deferProcessing = false;

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

Individual updatedIndy = [SELECT Id, ConsumerCreditScore FROM Individual WHERE Id = :indy.Id];
System.assertEquals(secondChild.AnnualRevenue, updatedIndy.ConsumerCreditScore);
}
}
106 changes: 106 additions & 0 deletions extra-tests/classes/RollupFullRecalcTests.cls
Original file line number Diff line number Diff line change
Expand Up @@ -2603,6 +2603,112 @@ private class RollupFullRecalcTests {
Assert.areEqual(10, parentTwo.NumberOfEmployees);
}

@IsTest
static void allowsMultipleRollupsToSameFieldWithDifferentBatches() {
// an interesting one because it's again an implementation detail - this time,
// of the batch "caboose" process. if the full recalc is BELOW the batch limit,
// the queueable will already correctly handle everything since the shared parent field will only be reset once
// batch processes, on the other hand, would otherwise simply see the existing value on the parent as something
// that needs to be cleared out
Rollup.defaultControl = new RollupControl__mdt(
MaxLookupRowsBeforeBatching__c = 1,
BatchChunkSize__c = 2,
IsRollupLoggingEnabled__c = true,
MaxRollupRetries__c = 1
);
Rollup.onlyUseMockMetadata = true;
Account parent = [SELECT Id FROM Account];

insert new List<SObject>{
new ContactPointAddress(ParentId = parent.Id, Name = 'child 1 first object', PreferenceRank = 25),
new Opportunity(AccountId = parent.Id, StageName = 'One', CloseDate = System.today(), Name = 'child 1 second object', Amount = 5),
new Opportunity(AccountId = parent.Id, StageName = 'Two', CloseDate = System.today(), Name = 'child 2 second object', Amount = 10)
};

Test.startTest();
Rollup.performBulkFullRecalc(
new List<Rollup__mdt>{
new Rollup__mdt(
CalcItem__c = 'ContactPointAddress',
LookupFieldOnCalcItem__c = 'ParentId',
RollupFieldOnCalcItem__c = 'PreferenceRank',
RollupOperation__c = 'SUM',
LookupObject__c = 'Account',
LookupFieldOnLookupObject__c = 'Id',
RollupFieldOnLookupObject__c = 'AnnualRevenue'
),
new Rollup__mdt(
CalcItem__c = 'Opportunity',
LookupFieldOnCalcItem__c = 'AccountId',
RollupFieldOnCalcItem__c = 'Amount',
RollupOperation__c = 'SUM',
LookupObject__c = 'Account',
LookupFieldOnLookupObject__c = 'Id',
RollupFieldOnLookupObject__c = 'AnnualRevenue'
)
},
Rollup.InvocationPoint.FROM_FULL_RECALC_LWC.name()
);
Test.stopTest();

Assert.areEqual(
2,
[SELECT COUNT() FROM AsyncApexJob WHERE JobType = 'BatchApexWorker' AND ApexClass.Name = :RollupFullBatchRecalculator.class.getName()],
'Test requires batch apex to have been the full recalc mechanism'
);
parent = [SELECT AnnualRevenue FROM Account WHERE Id = :parent.Id];
Assert.areEqual(40, parent.AnnualRevenue, 'Both children types should have correctly summed to one field in batch recalc');
}

@IsTest
static void allowsMultipleRollupsToSameFieldWithQueueable() {
Rollup.onlyUseMockMetadata = true;
Account parent = [SELECT Id FROM Account];
Account secondParent = new Account(Name = 'Second');
insert secondParent;

insert new List<SObject>{
new ContactPointAddress(ParentId = parent.Id, Name = 'child 1 first object', PreferenceRank = 25),
new Opportunity(AccountId = parent.Id, StageName = 'One', CloseDate = System.today(), Name = 'child 1 second object', Amount = 5),
new Opportunity(AccountId = parent.Id, StageName = 'Two', CloseDate = System.today(), Name = 'child 2 second object', Amount = 10),
new Opportunity(AccountId = secondParent.Id, StageName = 'Two', CloseDate = System.today(), Name = 'child 1 second object second parent', Amount = 10)
};

Test.startTest();
Rollup.performBulkFullRecalc(
new List<Rollup__mdt>{
new Rollup__mdt(
CalcItem__c = 'ContactPointAddress',
LookupFieldOnCalcItem__c = 'ParentId',
RollupFieldOnCalcItem__c = 'PreferenceRank',
RollupOperation__c = 'SUM',
LookupObject__c = 'Account',
LookupFieldOnLookupObject__c = 'Id',
RollupFieldOnLookupObject__c = 'AnnualRevenue'
),
new Rollup__mdt(
CalcItem__c = 'Opportunity',
LookupFieldOnCalcItem__c = 'AccountId',
RollupFieldOnCalcItem__c = 'Amount',
RollupOperation__c = 'SUM',
LookupObject__c = 'Account',
LookupFieldOnLookupObject__c = 'Id',
RollupFieldOnLookupObject__c = 'AnnualRevenue'
)
},
Rollup.InvocationPoint.FROM_FULL_RECALC_LWC.name()
);
Test.stopTest();

Assert.areEqual(
1,
[SELECT COUNT() FROM AsyncApexJob WHERE JobType = 'Queueable' AND ApexClass.Name = :RollupAsyncProcessor.class.getName()],
JSON.serializePretty([SELECT JobType, ApexClass.Name FROM AsyncApexJob])
);
parent = [SELECT AnnualRevenue FROM Account WHERE Id = :parent.Id];
Assert.areEqual(40, parent.AnnualRevenue, 'Both children types should have correctly summed to one field in full recalc');
}

private class DeferredContactPointAddressRollup extends RollupDeferredFullRecalcProcessor {
public DeferredContactPointAddressRollup(List<Rollup__mdt> meta, Set<String> recordIds) {
super(
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.5.73",
"version": "1.5.74",
"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
2 changes: 1 addition & 1 deletion rollup-namespaced/sfdx-project.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"default": true,
"package": "apex-rollup-namespaced",
"path": "rollup-namespaced/source/rollup",
"versionName": "Fixes an issue reported in #457 where using a Limit Amount in conjunction with first/last can throw an error if the number of children exceeds the limit amount",
"versionName": "Allows multiple objects to roll up to same parent field from batch full recalcs",
"versionNumber": "1.0.45.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",
Expand Down
3 changes: 2 additions & 1 deletion rollup/core/classes/Rollup.cls
Original file line number Diff line number Diff line change
Expand Up @@ -1822,7 +1822,8 @@ global without sharing virtual class Rollup implements RollupLogger.ToStringObje
// do the transforms
for (Rollup__mdt meta : cachedMetadata) {
meta.CalcItem__c = meta.CalcItem__r.QualifiedApiName;
meta.GroupByRowStartDelimiter__c = meta.GroupByRowStartDelimiter__c != null ? meta.GroupByRowStartDelimiter__c.unescapeJava() : null;
meta.GroupByRowEndDelimiter__c = meta.GroupByRowEndDelimiter__c?.unescapeJava();
meta.GroupByRowStartDelimiter__c = meta.GroupByRowStartDelimiter__c?.unescapeJava();
meta.LookupFieldOnCalcItem__c = meta.LookupFieldOnCalcItem__r.QualifiedApiName;
meta.LookupFieldOnLookupObject__c = meta.LookupFieldOnLookupObject__r.QualifiedApiName;
meta.LookupObject__c = meta.LookupObject__r.QualifiedApiName;
Expand Down
23 changes: 22 additions & 1 deletion rollup/core/classes/RollupAsyncProcessor.cls
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,16 @@ global virtual without sharing class RollupAsyncProcessor extends Rollup impleme
set;
}

protected final Set<String> uniqueParentFields {
get {
if (uniqueParentFields == null) {
uniqueParentFields = new Set<String>();
}
return uniqueParentFields;
}
set;
}

private static Map<Integer, Map<String, CalcItemBag>> CACHED_CALC_ITEM_LOOKUPS {
get {
if (CACHED_CALC_ITEM_LOOKUPS == null) {
Expand Down Expand Up @@ -718,7 +728,16 @@ global virtual without sharing class RollupAsyncProcessor extends Rollup impleme
}

protected Boolean parentRollupFieldHasBeenReset(RollupAsyncProcessor processor, SObject parent) {
return this.hasParentRollupFieldBeenReset(String.valueOf(processor.opFieldOnLookupObject), parent);
return this.hasParentRollupFieldBeenReset(String.valueOf(processor.opFieldOnLookupObject), parent) ||
this.uniqueParentFields.contains(processor.op.name() + processor.lookupObj + processor.opFieldOnLookupObject);
}

protected void storeUniqueParentFields(Rollup__mdt meta) {
if (meta != null) {
String uniqueParentKey = meta.RollupOperation__c + meta.LookupObject__c + meta.RollupFieldOnLookupObject__c;
this.uniqueParentFields.add(uniqueParentKey);
this.fullRecalcProcessor?.uniqueParentFields.add(uniqueParentKey);
}
}

protected virtual Set<String> getPreviouslyResetParents() {
Expand Down Expand Up @@ -1286,6 +1305,8 @@ global virtual without sharing class RollupAsyncProcessor extends Rollup impleme
private void handleFullRecalculators(RollupAsyncProcessor roll) {
if (roll instanceof RollupFullRecalcProcessor && roll.isProcessed == false) {
this.deferredRollups.add(roll);
} else {
this.storeUniqueParentFields(roll.metadata);
}
}

Expand Down
6 changes: 6 additions & 0 deletions rollup/core/classes/RollupFullRecalcProcessor.cls
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ global abstract without sharing class RollupFullRecalcProcessor extends RollupAs
public void finish() {
if (this.cabooses.isEmpty() == false) {
RollupFullRecalcProcessor conductor = this.cabooses.remove(0);
if (conductor.rollupMetas != null) {
for (Rollup__mdt meta : conductor.rollupMetas) {
conductor.storeUniqueParentFields(meta);
}
conductor.uniqueParentFields.addAll(this.uniqueParentFields);
}
for (RollupFullRecalcProcessor caboose : this.cabooses) {
conductor.addCaboose(caboose);
}
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.5.73';
private static final String CURRENT_VERSION_NUMBER = 'v1.5.74';
private static final LoggingLevel FALLBACK_LOGGING_LEVEL = LoggingLevel.DEBUG;
private static final RollupPlugin PLUGIN = new RollupPlugin();

Expand Down
7 changes: 4 additions & 3 deletions sfdx-project.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"default": true,
"package": "apex-rollup",
"path": "rollup",
"versionName": "Fixes an issue reported in #457 where using a Limit Amount in conjunction with first/last can throw an error if the number of children exceeds the limit amount",
"versionNumber": "1.5.73.0",
"versionName": "Allows multiple objects to roll up to same parent field from batch full recalcs",
"versionNumber": "1.5.74.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.5.70-0": "04t6g000008SnrXAAS",
"apex-rollup@1.5.71-0": "04t6g000008So61AAC",
"apex-rollup@1.5.72-0": "04t6g000008So6QAAS",
"apex-rollup@1.5.73-0": "04t6g000008So7xAAC"
"apex-rollup@1.5.73-0": "04t6g000008So7xAAC",
"apex-rollup@1.5.74-0": "04t6g000008So9AAAS"
}
}