Skip to content

Commit

Permalink
v1.1.6 - Stable for custom objects (#47)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
jamessimone authored Mar 4, 2021
1 parent c037156 commit efa2591
Show file tree
Hide file tree
Showing 17 changed files with 652 additions and 84 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,15 @@ To be clear - the following trigger contexts are necessary when using `runFromTr
- before delete
- after undelete

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>.

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.

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.

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

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!
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!

#### Rollup Custom Metadata Field Breakdown

Expand Down
49 changes: 35 additions & 14 deletions extra-tests/classes/RollupIntegrationTests.cls
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,15 @@ private class RollupIntegrationTests {
Rollup.records = [SELECT Id, Amount, AccountIdText__c FROM Opportunity];
Rollup.shouldRun = true;

FieldDefinition oppCustomField = [SELECT DurableId FROM FieldDefinition WHERE QualifiedApiName = 'AccountIdText__c' AND EntityDefinitionId = 'Opportunity'];
FieldDefinition accCustomField = [SELECT DurableId FROM FieldDefinition WHERE QualifiedApiName = 'AccountIdText__c' AND EntityDefinitionId = 'Account'];

Rollup.rollupMetadata = new List<Rollup__mdt>{
new Rollup__mdt(
RollupFieldOnCalcItem__c = 'Opportunity.Amount',
RollupFieldOnCalcItem__c = 'Amount',
LookupObject__c = 'Account',
LookupFieldOnCalcItem__c = oppCustomField.DurableId,
LookupFieldOnLookupObject__c = accCustomField.DurableId,
LookupFieldOnCalcItem__c = 'AccountIdText__c',
LookupFieldOnLookupObject__c = 'AccountIdText__c',
RollupFieldOnLookupObject__c = 'Account.AnnualRevenue',
RollupOperation__c = 'MAX'
RollupOperation__c = 'MAX',
CalcItem__c = 'Opportunity'
)
};

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

FieldDefinition oppCustomField = [SELECT DurableId FROM FieldDefinition WHERE QualifiedApiName = 'AmountFormula__c' AND EntityDefinitionId = 'Opportunity'];

Rollup.rollupMetadata = new List<Rollup__mdt>{
new Rollup__mdt(
RollupFieldOnCalcItem__c = oppCustomField.DurableId,
RollupFieldOnCalcItem__c = 'AmountFormula__c',
LookupObject__c = 'Account',
LookupFieldOnCalcItem__c = 'Opportunity.AccountId',
LookupFieldOnLookupObject__c = 'Account.Id',
RollupFieldOnLookupObject__c = 'Account.AnnualRevenue',
LookupFieldOnCalcItem__c = 'AccountId',
LookupFieldOnLookupObject__c = 'Id',
RollupFieldOnLookupObject__c = 'AnnualRevenue',
RollupOperation__c = 'SUM',
IsFullRecordSet__c = true,
CalcItemWhereClause__c = 'Name != \'' + opps[0].Name + '\'',
FullRecalculationDefaultNumberValue__c = 0
FullRecalculationDefaultNumberValue__c = 0,
CalcItem__c = 'Opportunity'
)
};

Expand All @@ -80,4 +77,28 @@ private class RollupIntegrationTests {
acc = [SELECT Id, AnnualRevenue FROM Account];
System.assertEquals(null, acc.AnnualRevenue, 'Formula field failed to be used correctly!');
}

@isTest
static void shouldSupportCustomObjectsReferencedViaCustomMetadata() {
Application__c app = new Application__c(Name = 'RollupIntegrationTests 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.shouldRun = true;
Rollup.apexContext = TriggerOperation.AFTER_INSERT;
// the CMDT record actually exists and is queried for; this test is to ensure that the custom object/field references
// get updated correctly in Rollup

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

app = [SELECT Objects__c FROM Application__c WHERE Id = :app.Id];
System.assertEquals('Lead, Account', app.Objects__c);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<label>RollupIntegrationTests</label>
<protected>false</protected>
<values>
<field>CalcItemWhereClause__c</field>
<value xsi:nil="true"/>
</values>
<values>
<field>CalcItem__c</field>
<value xsi:type="xsd:string">ApplicationLog__c</value>
</values>
<values>
<field>ChangedFieldsOnCalcItem__c</field>
<value xsi:nil="true"/>
</values>
<values>
<field>FullRecalculationDefaultNumberValue__c</field>
<value xsi:nil="true"/>
</values>
<values>
<field>FullRecalculationDefaultStringValue__c</field>
<value xsi:nil="true"/>
</values>
<values>
<field>IsFullRecordSet__c</field>
<value xsi:type="xsd:boolean">false</value>
</values>
<values>
<field>IsRollupStartedFromParent__c</field>
<value xsi:type="xsd:boolean">false</value>
</values>
<values>
<field>LookupFieldOnCalcItem__c</field>
<value xsi:type="xsd:string">Application__c</value>
</values>
<values>
<field>LookupFieldOnLookupObject__c</field>
<value xsi:type="xsd:string">Id</value>
</values>
<values>
<field>LookupObject__c</field>
<value xsi:type="xsd:string">Application__c</value>
</values>
<values>
<field>OrderByFirstLast__c</field>
<value xsi:nil="true"/>
</values>
<values>
<field>RollupControl__c</field>
<value xsi:type="xsd:string">Org_Defaults</value>
</values>
<values>
<field>RollupFieldOnCalcItem__c</field>
<value xsi:type="xsd:string">Object__c</value>
</values>
<values>
<field>RollupFieldOnLookupObject__c</field>
<value xsi:type="xsd:string">Objects__c</value>
</values>
<values>
<field>RollupOperation__c</field>
<value xsi:type="xsd:string">CONCAT_DISTINCT</value>
</values>
</CustomMetadata>
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
<actionOverrides>
<actionName>Accept</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Accept</actionName>
<formFactor>Large</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Accept</actionName>
<formFactor>Small</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>CancelEdit</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>CancelEdit</actionName>
<formFactor>Large</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>CancelEdit</actionName>
<formFactor>Small</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Clone</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Clone</actionName>
<formFactor>Large</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Clone</actionName>
<formFactor>Small</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Delete</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Delete</actionName>
<formFactor>Large</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Delete</actionName>
<formFactor>Small</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Edit</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Edit</actionName>
<formFactor>Large</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Edit</actionName>
<formFactor>Small</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>List</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>List</actionName>
<formFactor>Large</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>List</actionName>
<formFactor>Small</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>New</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>New</actionName>
<formFactor>Large</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>New</actionName>
<formFactor>Small</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>SaveEdit</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>SaveEdit</actionName>
<formFactor>Large</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>SaveEdit</actionName>
<formFactor>Small</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Tab</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Tab</actionName>
<formFactor>Large</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Tab</actionName>
<formFactor>Small</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>View</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>View</actionName>
<formFactor>Large</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>View</actionName>
<formFactor>Small</formFactor>
<type>Default</type>
</actionOverrides>
<allowInChatterGroups>false</allowInChatterGroups>
<compactLayoutAssignment>SYSTEM</compactLayoutAssignment>
<deploymentStatus>Deployed</deploymentStatus>
<enableActivities>false</enableActivities>
<enableBulkApi>true</enableBulkApi>
<enableFeeds>false</enableFeeds>
<enableHistory>false</enableHistory>
<enableLicensing>false</enableLicensing>
<enableReports>false</enableReports>
<enableSearch>false</enableSearch>
<enableSharing>true</enableSharing>
<enableStreamingApi>true</enableStreamingApi>
<label>Application Log</label>
<nameField>
<label>Application Log Name</label>
<type>Text</type>
</nameField>
<pluralLabel>Application Logs</pluralLabel>
<searchLayouts/>
<sharingModel>ReadWrite</sharingModel>
<visibility>Public</visibility>
</CustomObject>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Application__c</fullName>
<deleteConstraint>SetNull</deleteConstraint>
<externalId>false</externalId>
<label>Application</label>
<referenceTo>Application__c</referenceTo>
<relationshipLabel>Application Logs</relationshipLabel>
<relationshipName>Application_Logs</relationshipName>
<required>false</required>
<trackTrending>false</trackTrending>
<type>Lookup</type>
</CustomField>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Object__c</fullName>
<externalId>false</externalId>
<label>Object</label>
<length>255</length>
<required>false</required>
<trackTrending>false</trackTrending>
<type>Text</type>
<unique>false</unique>
</CustomField>
Loading

0 comments on commit efa2591

Please sign in to comment.