Skip to content

Commit

Permalink
v1.6.17 - Adds CMDT support for Task/User/Event (#570)
Browse files Browse the repository at this point in the history
* Upgrading @jongpie's sfdx-bummer-plugin dep to account for updated sf config syntax

* adding logging for deferred parent updates, consolidated some sorting logic

* Updating prettier

* Fixes an issue found while investigating #568 where multiple batch full recalcs to the same parent would not get properly chained together as cabooses

* Fixes #568 by allowing full recalc flows to start up from previously unsupported objects like Task/Event/User. Still need to add the corresponding validation rules to ensure the correct rollup fields are filled out, but otherwise this one is all set
  • Loading branch information
jamessimone authored Mar 5, 2024
1 parent 665cec0 commit c367983
Show file tree
Hide file tree
Showing 35 changed files with 2,261 additions and 471 deletions.
53 changes: 5 additions & 48 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=04t6g000008OaZVAA0">
<a href="https://login.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008OafFAAS">
<img alt="Deploy to Salesforce"
src="./media/deploy-package-to-prod.png">
</a>

<a href="https://test.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008OaZVAA0">
<a href="https://test.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008OafFAAS">
<img alt="Deploy to Salesforce Sandbox"
src="./media/deploy-package-to-sandbox.png">
</a>
Expand Down Expand Up @@ -77,7 +77,7 @@ You have several different options when it comes to making use of Apex Rollup:

## CMDT-based Rollup Solution

Custom Metadata Type (CMDT) records are the preferred way to configure and organize your rollups. There are some limitations to this approach, particularly for popular objects like Task and Event - expand the section below and navigate to the ["How To Configure Rollups When An Object Does Not Appear In The CMDT Dropdowns"](#taskeventuser-rollups---how-to-configure-rollups-when-an-object-does-not-appear-in-the-cmdt-dropdowns) section.
Custom Metadata Type (CMDT) records are the preferred way to configure and organize your rollups. There are some limitations to this approach, particularly for popular objects like Task and Event - if you don't see the object you'd like to use in either the parent or child dropdown(s), use the "Text" version of that field (eg `Child Object (Text)`).

<details>
<summary>Expand for CMDT-driven info</summary>
Expand Down Expand Up @@ -172,13 +172,6 @@ In addition to the above, some other considerations when it comes to the where c
- Any time a polymorphic field is used in your `Child Object Where Clause`, you must also have a constraint on the parent-level `Type` in order for it to work. If you are filtering on `Task.What`, for example, you must have only a single SObject-parent type as part of your where clause, e.g. `What.Name = 'someName' AND What.Type = 'Account'`.
- for rollups set up against objects with Large Data Volume (LDV - typically when the number of records for a given object exceed 300k), please note that full recalculations (either through the `REFRESH` context in Flow or through the `Recalculate Rollups` tab) are subject to the same query limits that exist elsewhere with SOQL; namely, that filtering on non-indexed fields can cause the initial batch recalculation process to timeout. If you receive an error with the message `REQUEST_RUNNING_TOO_LONG`, it's likely you're trying to roll values up using a where clause with non-indexed fields. Try changing your where clause to use indexed fields, or contact Salesforce Support to have a custom index created (as of Winter '23, you can also create custom indexes programmatically through the CLI, but I would only recommend this option to advanced users)

#### Task/Event/User Rollups - How To Configure Rollups When An Object Does Not Appear In The CMDT Dropdowns

There are several limitations to Entity Definition relationships when using Custom Metadata Types - most notably that they don't work with all objects. User, Task, Event, Case Comment, and others are excluded from the dropdowns generated by Entity Definition fields. However, you still have options when it comes to creating Rollups where these unsupported objects are the children or parent. Note that for both options, I've included YouTube links -.

1. You can use the base Invocable action - the one titled `Perform rollup on records` - as that one allows you to enter the values for the child/parent with text. [Here's an example of me filling out the base action on YouTube](https://youtu.be/jZy4gUKjw3Q?t=1133). If an object supports Record Triggered Flows but not the CMDT dropdowns, this is the perfect way to quickly get up and running
2. You can create simple Apex triggers - [I show off how to do this on YouTube](https://www.youtube.com/watch?v=RyQHXi5boW0&t=839s). That link will forward you to the exact timestamp where the Task/Event explanation begins, but the example is broadly applicable to any object that isn't supported via the built-in dropdowns

</details>

### Configure Custom Orderings With Rollup Order By Metadata
Expand Down Expand Up @@ -345,7 +338,7 @@ There is an included Lightning Web Component (LWC) that will show up in the "Cus

**Special notes on the recalc button**

- It relies on your rollups being configured using the `Rollup__mdt` CMDT. Unfortunately this means that it won't work for User/Task/Event-based rollups, or Rollups that are configured via the base Invocable Action (which uses text fields instead of `Rollup__mdt` records)
- It relies on your rollups being configured using the `Rollup__mdt` CMDT. Unfortunately this means that it won't work for Rollups that are configured via the base Invocable Action
- The button will not display on the flexipage at all until at least one `Rollup__mdt.LookupObject__c` field matches the SObject whose record flexipage you're dropping the button on
- The button _will_ display even if a given parent record has no matching children associated with the rollup(s) in question.
- This particular rollup runs synchronously, so it won't eat into your Asynchronous Job limits for the day; it also refreshes any Aura/page-layout sections of the page (LWC-based sections of the page should update automatically).
Expand Down Expand Up @@ -460,8 +453,7 @@ global static void runFromCDCTrigger()
global static void runFromApex(List<SObject> calcItems, TriggerOperation rollupContext)

// for more info on how this method differs from the one above it, check out the "Parent Level Merges" section!
// for anything OTHER than merge situations or rollups starting from Task, Event, or User, use of this method
// is an anti-pattern
// for anything OTHER than merge situations use of this method is an anti-pattern
global static Rollup runFromApex(List<Rollup__mdt> rollupMetadata, Evaluator eval, List<SObject> calcItems, Map<Id, SObject> oldCalcItems)

// imperatively from Apex with arguments taking the place of values previously supplied by CMDT
Expand Down Expand Up @@ -945,41 +937,6 @@ trigger ContactTrigger on Contact(after delete) {
}
```

If you are using record-triggered flows (or the invocable actions in general), _and_ your child records are targeting Task, Event, or User, this is one area you'll still need to conform to the above with some special caveats. While it's true that Custom Metadata `Rollup__mdt` records can't be created for these three objects, that doesn't mean those very same records can't be synthetically created in Apex (or that the globally exposed Apex rollup methods can't be used on those objects). To that effect, your corresponding `ContactTrigger` (or after delete method within your trigger handler class, since hopefully we're all using those ...) would look something like this:

```java
trigger ContactTrigger on Contact(after delete) {
// each of these Rollups should directly correspond to the equivalent invocable Rollup action
// this is because merge-related rollups will bypass your record-triggered flows and do the work
// directly within the Apex trigger. If you aren't operating on Task/Event/User as the child object,
// set up your rollups using CMDT and use "Rollup.runFromTrigger();" instead!
Rollup.batch(
Rollup.firstFromApex(
Task.Subject,
Task.WhoId,
Contact.Id,
Contact.Description,
Contact.SObjectType,
null, // default recalc value
new List<RollupOrderBy__mdt>{
new RollupOrderBy__mdt(
FieldName__c = 'ActivityDate',
Ranking__c = 0
)
},
RollupEvaluator.getWhereEval('Subject = \'Hello world!\'', Task.SObjectType)
),
Rollup.concatDistinctFromApex(
Event.Subject,
Event.WhoId,
Contact.Id,
Contact.Description,
Contact.SObjectType
)
);
}
```

### Change Data Capture (CDC)

As of [v1.0.4](https://github.com/jamessimone/apex-rollup/releases/tag/v1.0.4), CDC _is_ supported. However, at the moment Change Data Capture can be used strictly through CMDT, and requires a different one-liner for installation into your CDC object Trigger:
Expand Down
37 changes: 37 additions & 0 deletions extra-tests/classes/RollupFlowFullRecalcTests.cls
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,43 @@ private class RollupFlowFullRecalcTests {
System.assertEquals(secondParent.AnnualRevenue, updatedSecondParent.AnnualRevenue, 'Out of scope parent values should not have been affected');
}

@IsTest
static void shouldRefreshMultipleBatchFullRecalcs() {
Rollup.defaultControl = new RollupControl__mdt(MaxLookupRowsBeforeBatching__c = 1, IsRollupLoggingEnabled__c = true, MaxRollupRetries__c = 3);
Account acc = [SELECT Id FROM Account];
Individual secondParent = new Individual(LastName = 'Second');
insert secondParent;
List<ContactPointAddress> childrenToInsert = new List<ContactPointAddress>{
new ContactPointAddress(PreferenceRank = 500, ParentId = acc.Id, Name = 'A1'),
new ContactPointAddress(PreferenceRank = 500, ParentId = acc.Id, Name = 'A2'),
new ContactPointAddress(PreferenceRank = 250, ParentId = secondParent.Id, Name = 'B1'),
new ContactPointAddress(PreferenceRank = 250, ParentId = secondParent.Id, Name = 'B2')
};
insert childrenToInsert;

List<ContactPointAddress> cpas = new List<ContactPointAddress>{ childrenToInsert[0], childrenToInsert[2] };

List<Rollup.FlowInput> flowInputs = RollupTestUtils.prepareFlowTest(cpas, 'REFRESH', 'SUM');
flowInputs[0].deferProcessing = true;
flowInputs[0].calcItemWhereClause = 'PreferenceRank > 0';
List<Rollup.FlowInput> secondInputs = RollupTestUtils.prepareFlowTest(cpas, 'REFRESH', 'SUM');
secondInputs[0].rollupSObjectName = 'Individual';
secondInputs[0].rollupFieldOnOpObject = 'ConsumerCreditScore';
secondInputs[0].rollupOperation = 'SUM';
secondInputs[0].deferProcessing = true;

Test.startTest();
Rollup.performRollup(flowInputs);
Rollup.performRollup(secondInputs);
Rollup.processStoredFlowRollups();
Test.stopTest();

Account updatedAcc = [SELECT Id, AnnualRevenue FROM Account WHERE Id = :acc.Id];
System.assertEquals(1000, updatedAcc.AnnualRevenue);
Individual updatedSecondParent = [SELECT Id, ConsumerCreditScore FROM Individual WHERE Id = :secondParent.Id];
System.assertEquals(500, updatedSecondParent.ConsumerCreditScore);
}

@IsTest
static void shouldCorrectlyFilterParentFieldsFromFlowChildren() {
Account acc = [SELECT Id, Name FROM Account];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@
<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>ParentWhereClauseFromTrigger</label>
<protected>false</protected>
<values>
<field>CalcItemText__c</field>
<value xsi:type="xsd:string">Application__c</value>
</values>
<values>
<field>CalcItemWhereClause__c</field>
<value xsi:type="xsd:string">ParentApplication__r.Name = &apos;Parent&apos;</value>
</values>
<values>
<field>CalcItem__c</field>
<value xsi:type="xsd:string">Application__c</value>
<value xsi:nil="true"/>
</values>
<values>
<field>ChangedFieldsOnCalcItem__c</field>
Expand All @@ -18,6 +22,14 @@
<field>ConcatDelimiter__c</field>
<value xsi:nil="true"/>
</values>
<values>
<field>CurrencyFieldMapping__c</field>
<value xsi:nil="true"/>
</values>
<values>
<field>Description__c</field>
<value xsi:nil="true"/>
</values>
<values>
<field>FullRecalculationDefaultNumberValue__c</field>
<value xsi:nil="true"/>
Expand All @@ -30,6 +42,18 @@
<field>GrandparentRelationshipFieldPath__c</field>
<value xsi:nil="true"/>
</values>
<values>
<field>GroupByFields__c</field>
<value xsi:nil="true"/>
</values>
<values>
<field>GroupByRowEndDelimiter__c</field>
<value xsi:nil="true"/>
</values>
<values>
<field>GroupByRowStartDelimiter__c</field>
<value xsi:nil="true"/>
</values>
<values>
<field>IsFullRecordSet__c</field>
<value xsi:type="xsd:boolean">false</value>
Expand All @@ -39,17 +63,41 @@
<value xsi:type="xsd:boolean">false</value>
</values>
<values>
<field>LookupFieldOnCalcItem__c</field>
<field>IsTableFormatted__c</field>
<value xsi:type="xsd:boolean">false</value>
</values>
<values>
<field>LimitAmount__c</field>
<value xsi:nil="true"/>
</values>
<values>
<field>LookupFieldOnCalcItemText__c</field>
<value xsi:type="xsd:string">ParentApplication__c</value>
</values>
<values>
<field>LookupFieldOnLookupObject__c</field>
<field>LookupFieldOnCalcItem__c</field>
<value xsi:nil="true"/>
</values>
<values>
<field>LookupFieldOnLookupObjectText__c</field>
<value xsi:type="xsd:string">Id</value>
</values>
<values>
<field>LookupObject__c</field>
<field>LookupFieldOnLookupObject__c</field>
<value xsi:nil="true"/>
</values>
<values>
<field>LookupObjectText__c</field>
<value xsi:type="xsd:string">ParentApplication__c</value>
</values>
<values>
<field>LookupObject__c</field>
<value xsi:nil="true"/>
</values>
<values>
<field>OneToManyGrandparentFields__c</field>
<value xsi:nil="true"/>
</values>
<values>
<field>OrderByFirstLast__c</field>
<value xsi:nil="true"/>
Expand All @@ -59,13 +107,21 @@
<value xsi:type="xsd:string">Org_Defaults</value>
</values>
<values>
<field>RollupFieldOnCalcItem__c</field>
<field>RollupFieldOnCalcItemText__c</field>
<value xsi:type="xsd:string">Picklist__c</value>
</values>
<values>
<field>RollupFieldOnLookupObject__c</field>
<field>RollupFieldOnCalcItem__c</field>
<value xsi:nil="true"/>
</values>
<values>
<field>RollupFieldOnLookupObjectText__c</field>
<value xsi:type="xsd:string">Name</value>
</values>
<values>
<field>RollupFieldOnLookupObject__c</field>
<value xsi:nil="true"/>
</values>
<values>
<field>RollupOperation__c</field>
<value xsi:type="xsd:string">FIRST</value>
Expand All @@ -74,6 +130,14 @@
<field>RollupToUltimateParent__c</field>
<value xsi:type="xsd:boolean">false</value>
</values>
<values>
<field>SharingMode__c</field>
<value xsi:nil="true"/>
</values>
<values>
<field>ShouldRunWithoutCustomSettingEnabled__c</field>
<value xsi:type="xsd:boolean">false</value>
</values>
<values>
<field>SplitConcatDelimiterOnCalcItem__c</field>
<value xsi:type="xsd:boolean">false</value>
Expand Down
Loading

0 comments on commit c367983

Please sign in to comment.