Skip to content

Commit efa2591

Browse files
authored
v1.1.6 - Stable for custom objects (#47)
* Actual fix for #45 (support for custom objects), fixed issue where averages could blow up when a null value was present * Fixed a potential concat issue caused by trailing delimiters, added additional integration tests. * Fixed an issue with usage of RollupFlowBulkProcessor where an inner exception could get inadvertently eaten * Standardized how Rollup__mdt records are retrieved, fixing for good the issues present with custom fields/objects being used in Rollup__mdt entity definition and field definition fields - code review feedback from @jongpie
1 parent c037156 commit efa2591

17 files changed

+652
-84
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,15 @@ To be clear - the following trigger contexts are necessary when using `runFromTr
4242
- before delete
4343
- after undelete
4444

45+
This means that if you are invoking `Rollup.runFromTrigger();` from any other context (be it a quick action, LWC, Aura or wherever), nothing will happen; there won't be an error, but a rollup also won't be performed. For more information on one-off rollups, please see <a href="#calculating-rollup-after-install">Calculating Rollups After Install</a>.
46+
4547
The _only_ exception to the above is if you are using the `Is Rollup Started From The Parent` checkbox field on the `Rollup__mdt` custom metadata (<a href="#rollup-metadata-details">more details on that below</a>). If the rollup starts from the parent, you are free to only list the trigger contexts that make sense for you - for example, if you are initiating a rollup from parent records and the children records whose values you are rolling up are only ever updated when the parent is being inserted, you are free to use `after insert` in your Apex trigger if you have no need of the other contexts.
4648

4749
That's it! Now you're ready to configure your rollups using Custom Metadata. `Rollup` makes heavy use of Entity Definition & Field Definition metadata fields, which allows you to simply select your options from within picklists, or dropdowns. This is great for giving you quick feedback on which objects/fields are available without requiring you to know the API name for every SObject and their corresponding field names.
4850

4951
#### Special Considerations For Use Of Custom Fields As Rollup/Lookup Fields
5052

51-
One **special** thing to note on the subject of Field Definitions — custom fields referenced in CMDT Field Definition fields are stored in an atypical way, and require the usage of additional SOQL queries as part of `Rollup`'s upfront cost. A typical `Rollup` operation will use `2` SOQL queries per rollup — the query that determines whether or not a job should be queued or batched, and a query for the specific Rollup Limit metadata (a dynamic query, which unfortunately means that it counts against the SOQL limits) — prior to going into the async context (where all limits are renewed) plus `1` SOQL qery (also dynamic, which is why it contributes even though it's querying CMDT). However, usage of custom fields as any of the four fields referenced in the `Rollup__mdt` custom metadata (details below) adds an additional SOQL query. If the SOQL queries used by `Rollup` becomes cause for concern, please [submit an issue](/issues) and we can work to address it!
53+
One **special** thing to note on the subject of Field Definitions — custom fields/objects referenced in CMDT Field Definition fields are stored in an atypical way, and require the usage of an additional SOQL query as part of `Rollup`'s upfront cost. A typical `Rollup` operation will use `2` SOQL queries per rollup — the query that determines whether or not a job should be queued or batched, and a query to get the `Rollup__mdt` custom metadata. Though it's true that since Spring 21, it's possible to retrieve CMDT straight from the cache without consuming a SOQL query, Custom Metadata Types retrieved from the cache don't have their parent-level fields initialized, which makes it impossible to massage the data into a usable shape within the rest of `Rollup`. If the SOQL queries used by `Rollup` becomes cause for concern, please [submit an issue](/issues) and we can work to address it!
5254

5355
#### Rollup Custom Metadata Field Breakdown
5456

extra-tests/classes/RollupIntegrationTests.cls

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,15 @@ private class RollupIntegrationTests {
2222
Rollup.records = [SELECT Id, Amount, AccountIdText__c FROM Opportunity];
2323
Rollup.shouldRun = true;
2424

25-
FieldDefinition oppCustomField = [SELECT DurableId FROM FieldDefinition WHERE QualifiedApiName = 'AccountIdText__c' AND EntityDefinitionId = 'Opportunity'];
26-
FieldDefinition accCustomField = [SELECT DurableId FROM FieldDefinition WHERE QualifiedApiName = 'AccountIdText__c' AND EntityDefinitionId = 'Account'];
27-
2825
Rollup.rollupMetadata = new List<Rollup__mdt>{
2926
new Rollup__mdt(
30-
RollupFieldOnCalcItem__c = 'Opportunity.Amount',
27+
RollupFieldOnCalcItem__c = 'Amount',
3128
LookupObject__c = 'Account',
32-
LookupFieldOnCalcItem__c = oppCustomField.DurableId,
33-
LookupFieldOnLookupObject__c = accCustomField.DurableId,
29+
LookupFieldOnCalcItem__c = 'AccountIdText__c',
30+
LookupFieldOnLookupObject__c = 'AccountIdText__c',
3431
RollupFieldOnLookupObject__c = 'Account.AnnualRevenue',
35-
RollupOperation__c = 'MAX'
32+
RollupOperation__c = 'MAX',
33+
CalcItem__c = 'Opportunity'
3634
)
3735
};
3836

@@ -55,19 +53,18 @@ private class RollupIntegrationTests {
5553
Rollup.records = opps;
5654
Rollup.shouldRun = true;
5755

58-
FieldDefinition oppCustomField = [SELECT DurableId FROM FieldDefinition WHERE QualifiedApiName = 'AmountFormula__c' AND EntityDefinitionId = 'Opportunity'];
59-
6056
Rollup.rollupMetadata = new List<Rollup__mdt>{
6157
new Rollup__mdt(
62-
RollupFieldOnCalcItem__c = oppCustomField.DurableId,
58+
RollupFieldOnCalcItem__c = 'AmountFormula__c',
6359
LookupObject__c = 'Account',
64-
LookupFieldOnCalcItem__c = 'Opportunity.AccountId',
65-
LookupFieldOnLookupObject__c = 'Account.Id',
66-
RollupFieldOnLookupObject__c = 'Account.AnnualRevenue',
60+
LookupFieldOnCalcItem__c = 'AccountId',
61+
LookupFieldOnLookupObject__c = 'Id',
62+
RollupFieldOnLookupObject__c = 'AnnualRevenue',
6763
RollupOperation__c = 'SUM',
6864
IsFullRecordSet__c = true,
6965
CalcItemWhereClause__c = 'Name != \'' + opps[0].Name + '\'',
70-
FullRecalculationDefaultNumberValue__c = 0
66+
FullRecalculationDefaultNumberValue__c = 0,
67+
CalcItem__c = 'Opportunity'
7168
)
7269
};
7370

@@ -80,4 +77,28 @@ private class RollupIntegrationTests {
8077
acc = [SELECT Id, AnnualRevenue FROM Account];
8178
System.assertEquals(null, acc.AnnualRevenue, 'Formula field failed to be used correctly!');
8279
}
80+
81+
@isTest
82+
static void shouldSupportCustomObjectsReferencedViaCustomMetadata() {
83+
Application__c app = new Application__c(Name = 'RollupIntegrationTests App');
84+
insert app;
85+
86+
List<ApplicationLog__c> appLogs = new List<ApplicationLog__c>{
87+
new ApplicationLog__c(Application__c = app.Id, Object__c = 'Lead'),
88+
new ApplicationLog__c(Application__c = app.Id, Object__c = 'Account')
89+
};
90+
91+
Rollup.records = appLogs;
92+
Rollup.shouldRun = true;
93+
Rollup.apexContext = TriggerOperation.AFTER_INSERT;
94+
// the CMDT record actually exists and is queried for; this test is to ensure that the custom object/field references
95+
// get updated correctly in Rollup
96+
97+
Test.startTest();
98+
Rollup.runFromTrigger();
99+
Test.stopTest();
100+
101+
app = [SELECT Objects__c FROM Application__c WHERE Id = :app.Id];
102+
System.assertEquals('Lead, Account', app.Objects__c);
103+
}
83104
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
3+
<label>RollupIntegrationTests</label>
4+
<protected>false</protected>
5+
<values>
6+
<field>CalcItemWhereClause__c</field>
7+
<value xsi:nil="true"/>
8+
</values>
9+
<values>
10+
<field>CalcItem__c</field>
11+
<value xsi:type="xsd:string">ApplicationLog__c</value>
12+
</values>
13+
<values>
14+
<field>ChangedFieldsOnCalcItem__c</field>
15+
<value xsi:nil="true"/>
16+
</values>
17+
<values>
18+
<field>FullRecalculationDefaultNumberValue__c</field>
19+
<value xsi:nil="true"/>
20+
</values>
21+
<values>
22+
<field>FullRecalculationDefaultStringValue__c</field>
23+
<value xsi:nil="true"/>
24+
</values>
25+
<values>
26+
<field>IsFullRecordSet__c</field>
27+
<value xsi:type="xsd:boolean">false</value>
28+
</values>
29+
<values>
30+
<field>IsRollupStartedFromParent__c</field>
31+
<value xsi:type="xsd:boolean">false</value>
32+
</values>
33+
<values>
34+
<field>LookupFieldOnCalcItem__c</field>
35+
<value xsi:type="xsd:string">Application__c</value>
36+
</values>
37+
<values>
38+
<field>LookupFieldOnLookupObject__c</field>
39+
<value xsi:type="xsd:string">Id</value>
40+
</values>
41+
<values>
42+
<field>LookupObject__c</field>
43+
<value xsi:type="xsd:string">Application__c</value>
44+
</values>
45+
<values>
46+
<field>OrderByFirstLast__c</field>
47+
<value xsi:nil="true"/>
48+
</values>
49+
<values>
50+
<field>RollupControl__c</field>
51+
<value xsi:type="xsd:string">Org_Defaults</value>
52+
</values>
53+
<values>
54+
<field>RollupFieldOnCalcItem__c</field>
55+
<value xsi:type="xsd:string">Object__c</value>
56+
</values>
57+
<values>
58+
<field>RollupFieldOnLookupObject__c</field>
59+
<value xsi:type="xsd:string">Objects__c</value>
60+
</values>
61+
<values>
62+
<field>RollupOperation__c</field>
63+
<value xsi:type="xsd:string">CONCAT_DISTINCT</value>
64+
</values>
65+
</CustomMetadata>
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<actionOverrides>
4+
<actionName>Accept</actionName>
5+
<type>Default</type>
6+
</actionOverrides>
7+
<actionOverrides>
8+
<actionName>Accept</actionName>
9+
<formFactor>Large</formFactor>
10+
<type>Default</type>
11+
</actionOverrides>
12+
<actionOverrides>
13+
<actionName>Accept</actionName>
14+
<formFactor>Small</formFactor>
15+
<type>Default</type>
16+
</actionOverrides>
17+
<actionOverrides>
18+
<actionName>CancelEdit</actionName>
19+
<type>Default</type>
20+
</actionOverrides>
21+
<actionOverrides>
22+
<actionName>CancelEdit</actionName>
23+
<formFactor>Large</formFactor>
24+
<type>Default</type>
25+
</actionOverrides>
26+
<actionOverrides>
27+
<actionName>CancelEdit</actionName>
28+
<formFactor>Small</formFactor>
29+
<type>Default</type>
30+
</actionOverrides>
31+
<actionOverrides>
32+
<actionName>Clone</actionName>
33+
<type>Default</type>
34+
</actionOverrides>
35+
<actionOverrides>
36+
<actionName>Clone</actionName>
37+
<formFactor>Large</formFactor>
38+
<type>Default</type>
39+
</actionOverrides>
40+
<actionOverrides>
41+
<actionName>Clone</actionName>
42+
<formFactor>Small</formFactor>
43+
<type>Default</type>
44+
</actionOverrides>
45+
<actionOverrides>
46+
<actionName>Delete</actionName>
47+
<type>Default</type>
48+
</actionOverrides>
49+
<actionOverrides>
50+
<actionName>Delete</actionName>
51+
<formFactor>Large</formFactor>
52+
<type>Default</type>
53+
</actionOverrides>
54+
<actionOverrides>
55+
<actionName>Delete</actionName>
56+
<formFactor>Small</formFactor>
57+
<type>Default</type>
58+
</actionOverrides>
59+
<actionOverrides>
60+
<actionName>Edit</actionName>
61+
<type>Default</type>
62+
</actionOverrides>
63+
<actionOverrides>
64+
<actionName>Edit</actionName>
65+
<formFactor>Large</formFactor>
66+
<type>Default</type>
67+
</actionOverrides>
68+
<actionOverrides>
69+
<actionName>Edit</actionName>
70+
<formFactor>Small</formFactor>
71+
<type>Default</type>
72+
</actionOverrides>
73+
<actionOverrides>
74+
<actionName>List</actionName>
75+
<type>Default</type>
76+
</actionOverrides>
77+
<actionOverrides>
78+
<actionName>List</actionName>
79+
<formFactor>Large</formFactor>
80+
<type>Default</type>
81+
</actionOverrides>
82+
<actionOverrides>
83+
<actionName>List</actionName>
84+
<formFactor>Small</formFactor>
85+
<type>Default</type>
86+
</actionOverrides>
87+
<actionOverrides>
88+
<actionName>New</actionName>
89+
<type>Default</type>
90+
</actionOverrides>
91+
<actionOverrides>
92+
<actionName>New</actionName>
93+
<formFactor>Large</formFactor>
94+
<type>Default</type>
95+
</actionOverrides>
96+
<actionOverrides>
97+
<actionName>New</actionName>
98+
<formFactor>Small</formFactor>
99+
<type>Default</type>
100+
</actionOverrides>
101+
<actionOverrides>
102+
<actionName>SaveEdit</actionName>
103+
<type>Default</type>
104+
</actionOverrides>
105+
<actionOverrides>
106+
<actionName>SaveEdit</actionName>
107+
<formFactor>Large</formFactor>
108+
<type>Default</type>
109+
</actionOverrides>
110+
<actionOverrides>
111+
<actionName>SaveEdit</actionName>
112+
<formFactor>Small</formFactor>
113+
<type>Default</type>
114+
</actionOverrides>
115+
<actionOverrides>
116+
<actionName>Tab</actionName>
117+
<type>Default</type>
118+
</actionOverrides>
119+
<actionOverrides>
120+
<actionName>Tab</actionName>
121+
<formFactor>Large</formFactor>
122+
<type>Default</type>
123+
</actionOverrides>
124+
<actionOverrides>
125+
<actionName>Tab</actionName>
126+
<formFactor>Small</formFactor>
127+
<type>Default</type>
128+
</actionOverrides>
129+
<actionOverrides>
130+
<actionName>View</actionName>
131+
<type>Default</type>
132+
</actionOverrides>
133+
<actionOverrides>
134+
<actionName>View</actionName>
135+
<formFactor>Large</formFactor>
136+
<type>Default</type>
137+
</actionOverrides>
138+
<actionOverrides>
139+
<actionName>View</actionName>
140+
<formFactor>Small</formFactor>
141+
<type>Default</type>
142+
</actionOverrides>
143+
<allowInChatterGroups>false</allowInChatterGroups>
144+
<compactLayoutAssignment>SYSTEM</compactLayoutAssignment>
145+
<deploymentStatus>Deployed</deploymentStatus>
146+
<enableActivities>false</enableActivities>
147+
<enableBulkApi>true</enableBulkApi>
148+
<enableFeeds>false</enableFeeds>
149+
<enableHistory>false</enableHistory>
150+
<enableLicensing>false</enableLicensing>
151+
<enableReports>false</enableReports>
152+
<enableSearch>false</enableSearch>
153+
<enableSharing>true</enableSharing>
154+
<enableStreamingApi>true</enableStreamingApi>
155+
<label>Application Log</label>
156+
<nameField>
157+
<label>Application Log Name</label>
158+
<type>Text</type>
159+
</nameField>
160+
<pluralLabel>Application Logs</pluralLabel>
161+
<searchLayouts/>
162+
<sharingModel>ReadWrite</sharingModel>
163+
<visibility>Public</visibility>
164+
</CustomObject>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<fullName>Application__c</fullName>
4+
<deleteConstraint>SetNull</deleteConstraint>
5+
<externalId>false</externalId>
6+
<label>Application</label>
7+
<referenceTo>Application__c</referenceTo>
8+
<relationshipLabel>Application Logs</relationshipLabel>
9+
<relationshipName>Application_Logs</relationshipName>
10+
<required>false</required>
11+
<trackTrending>false</trackTrending>
12+
<type>Lookup</type>
13+
</CustomField>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<fullName>Object__c</fullName>
4+
<externalId>false</externalId>
5+
<label>Object</label>
6+
<length>255</length>
7+
<required>false</required>
8+
<trackTrending>false</trackTrending>
9+
<type>Text</type>
10+
<unique>false</unique>
11+
</CustomField>

0 commit comments

Comments
 (0)