In the Spring’15 release Salesforce provided the ability to subscribe to reports. This is done through a new Schedule button shown below. Users can schedule reports with criteria based on the report contents. When the platform runs the report, the criteria gets evaluated and if met will result in notifications sent to the user. The usual Email and Chatter options are available, as well Mobile Notifications for Salesforce1 mobile users, pretty cool!
It is the last notification option that caught my interest. Described simply as Execute Custom Action. This is in fact the gateway to many more possibilities, as it is essentially a peace of any Apex code. When the report is run and the criteria is met the platform will call your Apex code, a bit like an Apex Trigger but for Reports. Given the capabilities of Apex and what users can do with Salesforce Reports, thats not just pretty cool, that’s super cool!
Once the user saves the subscription the platform creates a scheduled entry that can be seen and deleted by administrators under the All Scheduled Jobs page under Setup, classified as Reporting Notification scheduled job type. The job will then run under the user context of the user that setup the subscription. Note that each user can only setup up to 5 report subscriptions presently.
Creating a Custom Action with Apex
In order for your Apex class to appear in the above UI for selection you must implement a platform provided Apex interface. The Reports.NotificationAction Apex interface is pretty simple, with just one execute method.
public with sharing class MyReportNotification implements Reports.NotificationAction { public void execute(Reports.NotificationActionContext context) { // Context parameter contains report instance and criteria Reports.ReportResults results = context.getReportInstance().getReportResults(); // In the above subscription case this is the 'Record Count' System.debug(context.getThresholdInformation().getEvaluatedConditions()[0].getValue(); // You can also access the report definition! System.debug(results.getReportMetadata().getName()); } }
If your familiar with the Analytics Apex API, you’ll soon feel quite at home with the information provided by the context parameter. The context parameter passes in an instance of the report itself, as in the records of the report. As well as a reminder of the criteria behind the subscription (as entered in the UI above) and the aggregate values that caused the criteria to be met. Also the report definition (its metadata), useful if you wanted to write a generic handler for example.
What about Clicks not Coders?
Now i love Apex coding, but i also know that this platform has not got where it has from just providing a programming language. Having a declarative means to deliver solutions rapidly with more pervasive skills is the corner stone of what makes the platform so successful. Tools like Process Builder and Visual Flow are a great example of this.
So you may think Salesforce have missed an opportunity here, by not providing a means to use for example an Autolaunched Flow as a Report subscription. I personally think so, hence i’ve raised this Idea Exchange idea here, please up vote if you agree. Meanwhile, all is not lost! As i’ve previously blogged there is a means to invoke Flow’s from Apex. So this got me wondering if Apex could be the glue between Salesforce Report Notifications and Flow?
Here is the result of a basic experiment of calling a Flow from an Apex Report Notification…
public with sharing class MyReportNotification implements Reports.NotificationAction { public void execute(Reports.NotificationActionContext context) { // Context parameter contains report instance and criteria Reports.ReportResults results = context.getReportInstance().getReportResults(); // Construct some parameters to pass to the Flow Map<String, Object> params = new Map<String, Object>(); params.put('ReportName', results.getReportMetadata().getName()); params.put('RecordCount', context.getThresholdInformation().getEvaluatedConditions()[0].getValue()); // Call the Flow with the parameters Flow.Interview.MyReportNotificationFlow reportNotificationFlow = new Flow.Interview.MyReportNotificationFlow(params); reportNotificationFlow.start(); } }
Implementation Note: The only drawback so far i can see is sadly you cannot pass the whole context parameter “as is” into Flow, as its variable types are quite limited, to make this more generic you would have to be quite inventive as to how you pass information.
The following shows the Autolaunched Flow called in the above example. It’s pretty simple but achieves something the other notification options cannot which is to create Task. If you want to know more see my blog here.
This results in a Task being created for the user as shown in the screenshot below. There is certainly a great potential for this type of approach, as it allows for clicks not coders to stretch their declarative skills without having to revisit the Apex code each time a new requirement or change is required.
Running, Testing and Debugging
If you look closely at the UI above you’ll see the button Save & Run Now. As the name suggests this will save the subscription details and run the report immediately. If the criteria is met your Apex class will be called immediately also. This is obviously pretty useful as the most granular scheduling period is on the hour.
When i did schedule the subscription as normal users would. I found oddly no evidence of it in the Apex Jobs page, despite a schedule appearing on the All Scheduled Jobs page. Its pretty obvious the platform runs these async, so i would have expected some evidence of this…
During my testing i found the Debug Logs output as expected (in both execution modes above). Though an important note is any exceptions thrown got swallowed when run from the UI, so always check your debug log if things are not working as expected.
Warning Possible Platform Bug: I did find that if my Apex code threw an exception when i used the UI to run my code it deleted any prior schedules for the report subscription. I had to delete and recreate the subscription to get the schedule entry back. I’ll investigate this a bit further and raise a case with Salesforce.
Writing an Apex Test for Report Notification Actions
Writing Apex Tests for the above is little different than the usual approach, mainly owing to the fact that the Analytics API requires that Apex tests leverage the SeeAllData attribute. This is clearly less than ideal, but is indeed documented as such in the formal Salesforce documentation.
In order to emulate the platform calling your execute method, you’ll need to mock the context parameter, fortunately it seems unlike some system types the context parameter type can be constructed, nice!
@IsTest private class MyReportNotificationTest { @IsTest(SeeAllData=true) private static void testMyReportNotification() { // Get an instance of the report Report report = [SELECT Id FROM Report WHERE DeveloperName = 'ClosedWon']; Test.startTest(); Reports.ReportInstance reportInstance = Reports.ReportManager.runAsyncReport(report.Id, true); Test.stopTest(); // Emulate the platform calling the custom action passing criteria that was met MyReportNotification myReportNotification = new MyReportNotification(); Reports.NotificationActionContext context = new Reports.NotificationActionContext( reportInstance, new Reports.ThresholdInformation( new List<Reports.EvaluatedCondition> { /* new Reports.EvaluatedCondition( 'RecordCount', 'Record Count', Double.valueOf(0), Double.valueOf(1), Reports.EvaluatedConditionOperator.GREATER_THAN) */ })); myReportNotification.execute(context); // Assert accordingly... // ... } }
Implementation Note: At time of writing, i found that i could not create a mock instance of the Reports.EvaluatedCondition type without receiving a compilation error claiming the constructor was not found (hence why its commented out). I’ll update this blog if i find out why. This issue will limit the testing of any criteria / threshold references in your code you make.
Summary
I love how Salesforce are opening up more of the platform to support Apex callbacks like this. It adds much more flexibility for developers to really extend the platform and not just solutions on top of it. In this case it also provided some flexibility for us to plug any gaps in the platforms current feature set. Salesforce please keep it up!
November 9, 2015 at 9:34 pm
Thanks for the excellent post.
Do you know if there is a way to create an instance of a flow dynamically?
Here is the scenario.
I would like write 1 class that my admins can tie to any report. The admins will set up metadata in the system (custom settings or custom metadata types) which will define the report name (or ID) and the flow that has to run with the results of the report. Based on the report, they would like to run different flows. I would like to read the metadata and dynamically instantiate the flow and run it with the parameters.
November 9, 2015 at 10:03 pm
Thanks! I am actually in the middle of building a packaged tool for this very purpose, it will be my second blog this month and the solution will be open source.
December 23, 2015 at 8:24 pm
I actually found a way to accomplish the defaults for your tests.
I ran into a similar situation where I was unable to create a mock instance to my object.
Here is the question and answer for it here: http://salesforce.stackexchange.com/questions/102338/test-class-for-quickaction-quickactiondefaultshandler
Once I remembered the ability to change a Map to an instance (like serializedUntyped), I knew it could work the same way.
I tried this out in Anonymous Apex and I am able to construct what you need.
List<Map> reportingConditionAsObjectList = new List<Map>
{
new Map
{
'aggregateName' => 'RecordCount',
'aggregateLabel' => 'Record Count',
'compareTo' => Double.valueOf(0),
'Value' => Double.valueOf(1),
'operator' => Reports.EvaluatedConditionOperator.GREATER_THAN
}
};
List defaultConditions =
(List)JSON.deserialize(JSON.serialize(reportingConditionAsObjectList), List.class);
December 23, 2015 at 8:26 pm
Awesome thanks for sharing! JSON to the rescue! 🙂
December 23, 2015 at 8:32 pm
Thank you! I’m so glad I can help someone who has contributed so much!
February 13, 2016 at 7:13 am
Amazing! Thank you both! I was going crazy trying to get the ReportsEvaluatedCondition list created. Constructor is documented but compiler ain’t playing nice =/
February 13, 2016 at 8:27 am
Your very welcome, cool use case in your blog btw!
December 23, 2015 at 8:29 pm
I actually had a similar situation with the following question I posted: http://salesforce.stackexchange.com/questions/102338/test-class-for-quickaction-quickactiondefaultshandler
Turns out, you can use JSON.serialize to create instance variables that are difficult to mock.
Once I realized that I can change a Map into an instance of my choosing, it actually becomes pretty straight-forward.
So the following code should suffice for you test class:
List<Map> reportingConditionAsObjectList = new List<Map>
{
new Map
{
'aggregateName' => 'RecordCount',
'aggregateLabel' => 'Record Count',
'compareTo' => Double.valueOf(0),
'Value' => Double.valueOf(1),
'operator' => Reports.EvaluatedConditionOperator.GREATER_THAN
}
};
List defaultConditions =
(List)JSON.deserialize(JSON.serialize(reportingConditionAsObjectList), List.class);
Pingback: Salesforce: Automate Adding Contacts to Campaign via Report Subscription | Doug Ayers
September 22, 2016 at 6:02 pm
Andrew,
Is there a way to send an email to the user instead of creating a Task? I’m using a custom Action because the Subect and the body of the message needs to be customized, I created an apex class that implements a notification Action, just like you did and I pass the parameters to the Flow. Once I’m on the Flow, I don’t know how to tell the Flow to grab the parameters I’m trying to pass.
What does your TaskReminder looks like behind the scene in your Flow?
September 26, 2016 at 9:28 am
You can use the Email element in flow to send an email, i have also shared the flow definition for the TaskReminder so you an upload it into your org and take a look.
– TaskReminder Flow, https://gist.github.com/afawcett/59595a58416cd339cc6d3fd7a2341ffb
– Email in Flow, https://help.salesforce.com/HTViewHelpDoc?id=vpm_designer_elements_action_email_flow.htm&language=en_US
December 15, 2016 at 5:42 pm
Hi Andrew. Thanks for the post. I’m going to create a great tool for Admins to provide the ability to schedule record updates. Workflow only allows Admins to easily schedule one action but not a repeating action.
My idea is to have Admins use the report engine to specify the criteria.
Place the record id in the first column and a datetime field to update in the second column.
Schedule a generic Apex script which will add a timestamp to all of the designated fields. This will allow their process builders and flows to fire 🙂
I will probably need to make them batchable to avoid flow errors (101 soql errors). But I think that could be such a useful tips for Admins looking to schedule
December 16, 2016 at 8:15 am
Sounds great! Take a look at my Flow Toolkit for dynamically invoking Flows. https://www.google.co.uk/amp/s/andyinthecloud.com/2016/02/28/dynamically-creating-flows-in-apex/amp/?client=safari
December 17, 2016 at 6:55 am
Hi Andrew, Thanks for that. I built up the first draft, but have hit one major snag. It looks like the it only passes the first 2000 records of the report to the Apex. Does that sound right to you?
February 16, 2017 at 2:50 am
Yeah that’s a platform limit. Anther thought would be to have them give a list view name and use the list view API to get the soql generated by the platform and use that to drive your batch Apex.
December 15, 2016 at 5:43 pm
Hi Andrew. Thanks for the post. I’m going to create a great tool for Admins to provide the ability to schedule record updates. Workflow only allows Admins to easily schedule one action but not a repeating action.
My idea is to have Admins use the report engine to specify the criteria.
Place the record id in the first column and a datetime field to update in the second column.
Schedule a generic Apex script which will add a timestamp to all of the designated fields. This will allow their process builders and flows to fire 🙂
I will probably need to make them batchable to avoid flow errors (101 soql errors). But I think that could be such a useful tips for Admins looking to schedule
Pingback: An API junkies perspective on Spring’17 | Andy in the Cloud
May 5, 2017 at 4:10 pm
Thanks for the post, this is awesome!!!
I actually have a question related to this. Is there a way to identify which all reports are subscribed for report notifications (like chatter notification, email notification, apex execution etc..,)?
May 6, 2017 at 2:20 am
I checked the Apex Analytics API and did not see it in the Report metadata. I checked the REST Analytics API and found this, https://developer.salesforce.com/docs/atlas.en-us.api_analytics.meta/api_analytics/analytics_api_notification_example_get_notifications.htm. Its worth setting up a report and retrieving its JSON via the Salesforce Developer Workbench and seeing what you can see.
Pingback: A Hidden Gem To Trigger Alerts | Salesforce Weekly
April 21, 2018 at 1:35 am
I really like this post firing action and flows from a condition in a report but what Im not sure about is if I can think of any use cases for this that could not be achieved through a process builder calling a flow
April 21, 2018 at 11:15 am
Process Builder is based on Record Edits, these are based on Report Data changes, so the later is more flexible given the possibilities of reporting over many objects, summaries, formulas etc
Pingback: A Hidden Gem To Trigger Alerts – Salesforce Weekly
April 11, 2019 at 7:41 am
So i am not so tech savy, but is this even related to send out report to email when rows >2000
April 27, 2019 at 7:04 am
I believe this will still be limited by the general reporting limit of 2000 rows. I would double check the api docs linked from this blog though to be sure.
January 29, 2020 at 1:28 pm
Hi Andrew,
I’m new to Salesforce and Apex (for sure). Was browsing and found your blog.
I’ve a report which shows few Opportunities and its owners based on the filters criteria applied.
Would it be possible to notify ONLY those owners who are listed in the report with Apex code ? We are in Spring Release ’20, wondering if the capability to notify the based on the report results is available in latest releases without Apex Coding.
Appreciate your help.
April 2, 2020 at 10:31 am
That sounds like a really good use case. I am not aware of a way of doing that without Apex though.