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.2.0 - Grand(and greater)parent rollups #57

Merged
merged 33 commits into from
Mar 19, 2021
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
9dec4af
Initial commit with partially abstracted relationship name framework
jamessimone Mar 5, 2021
54e4ac2
Getting more object-oriented by the second!
jamessimone Mar 5, 2021
b343c88
Keeping it clean
jamessimone Mar 5, 2021
dcd59c7
Making instance variable explicitly referenced
jamessimone Mar 5, 2021
6ebe7ba
Even though this won't work for objects with more than one lookup pat…
jamessimone Mar 6, 2021
8122862
Deprecated RollupControl__mdt.MaxLookupRowsForQueueable__c in favor o…
jamessimone Mar 6, 2021
1bd665a
Fixed typo in Readme, got tests back to green after MaxParentRowsUpda…
jamessimone Mar 8, 2021
78f1b3a
begin refactor toward usage of QueryWrapper object to prevent string …
jamessimone Mar 9, 2021
14832d5
Added parent recalc integration test with custom fields
jamessimone Mar 9, 2021
efcd4c8
Actually make use of stackCount variable
jamessimone Mar 9, 2021
a8fd269
First pass at fixing RollupRelationshipFieldFinder to actually traver…
jamessimone Mar 10, 2021
e379c5f
Major cleanup of RollupRelationshipFinder and tests, added in ability…
jamessimone Mar 10, 2021
c184d86
Updating test/build script to properly take into account test command…
jamessimone Mar 10, 2021
5f018e0
Further progress on #46 - start of work to wire RollupRelationshipFie…
jamessimone Mar 12, 2021
c96360e
Added further sanity tests for RollupRelationshipFieldFinder validati…
jamessimone Mar 13, 2021
f7fe0c3
Got rid of flow and flow test now that patch 230.12.2 has hit and fix…
jamessimone Mar 13, 2021
357cc33
Started wiring up Rollup to RollupRelationshipFieldFinder, added Roll…
jamessimone Mar 13, 2021
5e23ffe
First passing integration test for #46. still some cleanup/tweaks to …
jamessimone Mar 13, 2021
e01aa05
Cleaning up grandparent rollups, bulk optimization for grandparent ro…
jamessimone Mar 14, 2021
7fcf088
Added passthrough / grandparent functionality to each invocable. Prop…
jamessimone Mar 14, 2021
8418556
WIP - close to the end, but some issues to figure out specific to rep…
jamessimone Mar 14, 2021
674867e
Nearing completion - added traversal.getOldUltimateParent() method so…
jamessimone Mar 15, 2021
c92f17a
Got reparenting for grandparent rollups working!
jamessimone Mar 15, 2021
333bc7a
Fixing cached testing metadata issue, updated folder directory in ext…
jamessimone Mar 16, 2021
c159a2a
Updating comments
jamessimone Mar 16, 2021
6d70a2a
Parent-initiated grandparent rollups now working, with the caveat tha…
jamessimone Mar 17, 2021
dfd16a1
Final updates for #46 - fixed recommencement bug with RollupRelations…
jamessimone Mar 17, 2021
0d9d7fc
Updating readme with parent-initiated great-grandparent rollup note
jamessimone Mar 17, 2021
2e62fc6
Cleanup
jamessimone Mar 17, 2021
be7cabe
Excellent feedback from @jongpie - made Traversal constructor private…
jamessimone Mar 18, 2021
7fab78a
Fixing last of the feedback from @jongpie - if intermediate parents a…
jamessimone Mar 19, 2021
a257677
Fixed README typo, added comment explaining non-idiomatic CDC behavior
jamessimone Mar 19, 2021
3dd60c6
Prettier formatting
jamessimone Mar 19, 2021
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
56 changes: 46 additions & 10 deletions README.md

Large diffs are not rendered by default.

223 changes: 220 additions & 3 deletions extra-tests/classes/RollupIntegrationTests.cls
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
@isTest
private class RollupIntegrationTests {
// "Integration," in the sense that these include custom fields that shouldn't be installed
// we still don't need to actually update the records to prove the point
// "Integration," in the sense that these include custom fields / objects that shouldn't be installed
@TestSetup
static void setup() {
Rollup.defaultControl = new RollupControl__mdt(ShouldAbortRun__c = true);
Expand Down Expand Up @@ -142,6 +141,224 @@ private class RollupIntegrationTests {
Test.stopTest();

ParentApplication__c updatedParent = [SELECT Engagement_Rollup__c FROM ParentApplication__c];
System.assertEquals(45/3, updatedParent.Engagement_Rollup__c, 'Average should be calculated based off of all items for denominator, not just matching items');
System.assertEquals(45/3, updatedParent.Engagement_Rollup__c, 'Average should be calculated based off of matching items');
}

@isTest
static void shouldSupportCustomObjectsWhenRollupTriggeredFromParent() {
ParentApplication__c parentApp = new ParentApplication__c(Name = 'Custom Object Parent App');
insert parentApp;

List<Application__c> apps = new List<Application__c>{
new Application__c(Something_With_Underscores__c = 'We have and in the name', ParentApplication__c = parentApp.Id, Engagement_Score__c = 40),
new Application__c(Something_With_Underscores__c = 'We have and in the name', ParentApplication__c = parentApp.Id, Engagement_Score__c = 40),
new Application__c(Something_With_Underscores__c = 'Financial Services', ParentApplication__c = parentApp.Id, Engagement_Score__c = 30),
new Application__c(Something_With_Underscores__c = 'Backslashes/Too', ParentApplication__c = parentApp.Id, Engagement_Score__c = 5),
new Application__c(Something_With_Underscores__c = 'Something & Something Else', ParentApplication__c = parentApp.Id, Engagement_Score__c = 10)
};
insert apps;

Rollup.FlowInput input = new Rollup.FlowInput();
input.lookupFieldOnCalcItem = 'ParentApplication__c';
input.lookupFieldOnOpObject = 'Id';
input.recordsToRollup = new List<ParentApplication__c>{ parentApp };
input.rollupContext = 'INSERT';
input.rollupFieldOnCalcItem = 'Engagement_Score__c';
input.rollupFieldOnOpObject = 'Engagement_Rollup__c';
input.rollupOperation = 'SUM';
input.rollupSObjectName = 'ParentApplication__c';
input.isRollupStartedFromParent = true;
input.calcItemTypeWhenRollupStartedFromParent = 'Application__c';

Test.startTest();
Rollup.performRollup(new List<Rollup.FlowInput>{
input
});
Test.stopTest();

ParentApplication__c updatedParent = [SELECT Engagement_Rollup__c FROM ParentApplication__c];
System.assertEquals(125, updatedParent.Engagement_Rollup__c, 'Custom fields should work when rollup started from parent!');
}

/** grandparent rollup tests */
@isTest
static void shouldFindGreatGrandParentRelationshipBetweenCustomObjects() {
Account greatGrandparent = new Account(Name = 'Great-grandparent');
Account secondGreatGrandparent = new Account(Name = 'Second great-grandparent');
insert new List<Account>{ greatGrandparent, secondGreatGrandparent };

ParentApplication__c grandParent = new ParentApplication__c(Name = 'Grandparent', Account__c = greatGrandparent.Id);
ParentApplication__c nonMatchingGrandParent = new ParentApplication__c(Name = 'Non-matching grandparent');
insert new List<ParentApplication__c>{ grandParent, nonMatchingGrandParent };

Application__c parent = new Application__c(Name = 'Parent', ParentApplication__c = grandParent.Id);
Application__c nonMatchingParent = new Application__c(Name = 'Non matching parent', ParentApplication__c = nonMatchingGrandParent.Id);
insert new List<Application__c>{ parent, nonMatchingParent };

ApplicationLog__c child = new ApplicationLog__c(Application__c = parent.Id, Name = 'Test Rollup Grandchildren');
ApplicationLog__c nonMatchingChild = new ApplicationLog__c(Name = 'Non matching child', Application__c = nonMatchingParent.Id);
List<ApplicationLog__c> appLogs = new List<ApplicationLog__c>{ child, nonMatchingChild };
insert appLogs;

RollupRelationshipFieldFinder finder = new RollupRelationshipFieldFinder(
new RollupControl__mdt(MaxQueryRows__c = 1000),
'Application__r.ParentApplication__r.Account__r.Name',
new Set<String>{ 'Id', 'Name' },
Account.SObjectType,
new Map<Id, SObject>()
);

RollupRelationshipFieldFinder.Traversal traversal = finder.getParents(appLogs);
System.assertEquals(true, traversal.getIsFinished(), 'Traversal should not have aborted early');
System.assertEquals(greatGrandparent, traversal.retrieveParent(child.Id), 'Account should match!');

System.assertEquals(
null,
traversal.retrieveParent(nonMatchingChild.Id),
'No matching records should be returned for relationship that does not go fully up the chain'
);

// ok, and can we access the great-grandparent if the lookup field is populated?
nonMatchingGrandParent.Account__c = secondGreatGrandparent.Id;
update nonMatchingGrandParent;

// this also validates that the internal state of the finder is resilient; that it can be called more than once
traversal = finder.getParents(appLogs);
System.assertEquals(greatGrandparent, traversal.retrieveParent(child.Id), 'Should still match!');
System.assertEquals(secondGreatGrandparent, traversal.retrieveParent(nonMatchingChild.Id), 'Should now match!');
}

@isTest
static void shouldNotBlowUpIfGrandparentsDontExist() {
Application__c app = new Application__c(Name = 'No grandparent app');
insert app;

List<ApplicationLog__c> appLogs = new List<ApplicationLog__c>{
new ApplicationLog__c(Application__c = app.Id, Object__c = 'Lead'),
new ApplicationLog__c(Application__c = app.Id, Object__c = 'Account')
};

Rollup.records = appLogs;
Rollup.rollupMetadata = new List<Rollup__mdt>{
new Rollup__mdt(
CalcItem__c = 'ApplicationLog__c',
RollupFieldOnCalcItem__c = 'Object__c',
LookupFieldOnCalcItem__c = 'Application__c',
LookupObject__c = 'Account',
LookupFieldOnLookupObject__c = 'Id',
RollupFieldOnLookupObject__c = 'Name',
RollupOperation__c = 'CONCAT',
GrandparentRelationshipFieldPath__c = 'Application__r.ParentApplication__r.Account__r.Name'
)
};
Rollup.shouldRun = true;
Rollup.apexContext = TriggerOperation.AFTER_INSERT;

Test.startTest();
Rollup.runFromTrigger();
Test.stopTest();

// basically validates that traversal.isAbortedEarly correctly does its job in RollupRleationshipFieldFinder.cls
System.assert(true, 'Should make it here without exception being thrown');
}

@isTest
static void shouldRunCorrectlyForGrandparentReparenting() {
Account greatGrandparent = new Account(Name = 'Great-grandparent');
Account secondGreatGrandparent = new Account(Name = 'Second great-grandparent');
insert new List<Account>{ greatGrandparent, secondGreatGrandparent };

ParentApplication__c grandParent = new ParentApplication__c(Name = 'Grandparent', Account__c = greatGrandparent.Id);
ParentApplication__c secondGrandparent = new ParentApplication__c(Name = 'Second grandparent', Account__c = secondGreatGrandparent.Id);
insert new List<ParentApplication__c>{ grandParent, secondGrandparent };

Application__c parent = new Application__c(Name = 'Parent-level', ParentApplication__c = grandParent.Id);
Application__c secondParent = new Application__c(Name = 'Second parent-level', ParentApplication__c = secondGrandparent.Id);
insert new List<Application__c>{ parent, secondParent };

ApplicationLog__c child = new ApplicationLog__c(Application__c = secondParent.Id, Name = 'Test Rollup Grandchildren Reparenting');
ApplicationLog__c secondChild = new ApplicationLog__c(Name = 'Reparenting deux', Application__c = parent.Id);
List<ApplicationLog__c> appLogs = new List<ApplicationLog__c>{ child, secondChild };
insert appLogs;

Rollup.records = appLogs;
Rollup.rollupMetadata = new List<Rollup__mdt>{
new Rollup__mdt(
CalcItem__c = 'ApplicationLog__c',
RollupFieldOnCalcItem__c = 'Name',
LookupFieldOnCalcItem__c = 'Application__c',
LookupObject__c = 'Account',
LookupFieldOnLookupObject__c = 'Id',
RollupFieldOnLookupObject__c = 'Name',
RollupOperation__c = 'CONCAT_DISTINCT',
GrandparentRelationshipFieldPath__c = 'Application__r.ParentApplication__r.Account__r.Name'
)
};
Rollup.shouldRun = true;
Rollup.apexContext = TriggerOperation.AFTER_UPDATE;
Rollup.oldRecordsMap = new Map<Id, SObject>{
child.Id => new ApplicationLog__c(Id = child.Id, Application__c = parent.Id, Name = greatGrandparent.Name),
secondChild.Id => new ApplicationLog__c(Id = secondChild.Id, Application__c = secondParent.Id, Name = secondGreatGrandparent.Name)
};

Test.startTest();
Rollup.runFromTrigger();
Test.stopTest();

Account updatedGreatGrandparent = [SELECT Name FROM Account WHERE Id = :greatGrandparent.Id];
Account updatedGreatGrandparentTwo = [SELECT Name FROM Account WHERE Id = :secondGreatGrandparent.Id];

System.assertEquals(secondChild.Name, updatedGreatGrandparent.Name, 'CONCAT_DISTINCT and reparenting should have worked');
System.assertEquals(child.Name, updatedGreatGrandparentTwo.Name, 'CONCAT_DISTINCT and reparenting should have worked again');
}

@isTest
static void shouldRunGrandparentRollupsWhenIntermediateObjectsAreUpdated() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Niiiiiice

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So glad you tested that scenario

Account greatGrandparent = new Account(Name = 'Great-grandparent');
Account secondGreatGrandparent = new Account(Name = 'Second great-grandparent');
insert new List<Account>{ greatGrandparent, secondGreatGrandparent };

ParentApplication__c grandParent = new ParentApplication__c(Name = 'Grandparent', Account__c = greatGrandparent.Id);
ParentApplication__c secondGrandparent = new ParentApplication__c(Name = 'Second grandparent', Account__c = secondGreatGrandparent.Id);
List<ParentApplication__c> parentApps = new List<ParentApplication__c>{ grandParent, secondGrandparent };
insert parentApps;

Application__c parent = new Application__c(Name = 'Parent-level', ParentApplication__c = grandParent.Id);
Application__c secondParent = new Application__c(Name = 'Second parent-level', ParentApplication__c = secondGrandparent.Id);
insert new List<Application__c>{ parent, secondParent };

ApplicationLog__c child = new ApplicationLog__c(Application__c = secondParent.Id, Name = 'Test Rollup Grandchildren Reparenting');
ApplicationLog__c secondChild = new ApplicationLog__c(Name = 'Reparenting deux', Application__c = parent.Id);
insert new List<ApplicationLog__c>{ child, secondChild };

Rollup.records = parentApps;
Rollup.rollupMetadata = new List<Rollup__mdt>{
new Rollup__mdt(
CalcItem__c = 'ApplicationLog__c',
RollupFieldOnCalcItem__c = 'Name',
LookupFieldOnCalcItem__c = 'Application__c',
LookupObject__c = 'Account',
LookupFieldOnLookupObject__c = 'Id',
RollupFieldOnLookupObject__c = 'Name',
RollupOperation__c = 'CONCAT_DISTINCT',
GrandparentRelationshipFieldPath__c = 'Application__r.ParentApplication__r.Account__r.Name'
)
};
Rollup.shouldRun = true;
Rollup.apexContext = TriggerOperation.AFTER_UPDATE;
Rollup.oldRecordsMap = new Map<Id, SObject>{
grandParent.Id => new ParentApplication__c(Id = grandParent.Id, Account__c = secondGreatGrandparent.Id),
secondGrandparent.Id => new ParentApplication__c(Id = secondGrandparent.Id, Account__c = greatGrandparent.Id)
};

Test.startTest();
Rollup.runFromTrigger();
Test.stopTest();

Account updatedGreatGrandparent = [SELECT Name FROM Account WHERE Id = :greatGrandparent.Id];
Account updatedGreatGrandparentTwo = [SELECT Name FROM Account WHERE Id = :secondGreatGrandparent.Id];

System.assertEquals(secondChild.Name, updatedGreatGrandparent.Name, 'Grandparent record should have retriggered greatgrandparent rollup!');
System.assertEquals(child.Name, updatedGreatGrandparentTwo.Name, 'Grandparent record should have retriggered greatgrandparent rollup again!');
}
}
Binary file added media/example-grandparent-rollup.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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.1.11",
"version": "1.2.0",
"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
Loading