Skip to content

Commit

Permalink
Added support for Run As User and Run Report Triggers from Process Bu…
Browse files Browse the repository at this point in the history
…ilder
  • Loading branch information
afawcettffdc committed Sep 7, 2015
1 parent fda34ab commit a743fff
Show file tree
Hide file tree
Showing 15 changed files with 311 additions and 15 deletions.
58 changes: 58 additions & 0 deletions src/classes/EmailServicesAddressesSelector.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* Copyright (c), Cory Cowgill and Andrew Fawcett
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the Cory Cowgill and Andrew Fawcett, nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**/

/**
* Class encapsulates query logic for EmailServicesAddress
*
* https://developer.salesforce.com/page/Apex_Enterprise_Patterns_-_Selector_Layer
**/
public class EmailServicesAddressesSelector extends fflib_SObjectSelector
{
public EmailServicesAddressesSelector() {
super(false, false, false);
}

public List<Schema.SObjectField> getSObjectFieldList()
{
return new List<Schema.SObjectField> {
EmailServicesAddress.Id,
EmailServicesAddress.LocalPart,
EmailServicesAddress.EmailDomainName
};
}

public Schema.SObjectType getSObjectType()
{
return EmailServicesAddress.sObjectType;
}

public List<EmailServicesAddress> selectByUser(Set<Id> userIds) {
String functionName = 'LittleBitsSubscriberInboundEmailHandler';
return Database.query(
newQueryFactory().setCondition(
'Function.FunctionName = :functionName And IsActive = true and RunAsUserId in :userIds').toSOQL());
}
}
4 changes: 4 additions & 0 deletions src/classes/EmailServicesAddressesSelector.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>31.0</apiVersion>
</ApexClass>
60 changes: 60 additions & 0 deletions src/classes/LittleBitsActionReportTrigger.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Copyright (c), Andrew Fawcett, inc
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the Andrew Fawcett, inc nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**/

global with sharing class LittleBitsActionReportTrigger {

global class Request {
@InvocableVariable(Label='Unique Name' Description='Unique Name of the Report Trigger' Required=true)
global String UniqueName;
}

/**
* Run the given Report Trigger
**/
@InvocableMethod(Label='Runs the given Report Trigger' Description='Runs the given Report Trigger.')
global static void run(List<Request> requests) {
Set<String> uniqueNames = new Set<String>();
for(Request request : requests)
uniqueNames.add(request.UniqueName);
System.enqueueJob(new RunReportTriggerJob(uniqueNames));
}

/**
* Run the Report Triggers in Async since Invocable Methods from Process Builder don't support callouts
**/
public class RunReportTriggerJob implements Queueable, Database.AllowsCallouts {

private Set<String> uniqueNames = new Set<String>();

public RunReportTriggerJob(Set<String> uniqueNames) {
this.uniqueNames = uniqueNames;
}

public void execute(QueueableContext context) {
LittleBitsService.runReportTriggers(uniqueNames);
}
}
}
4 changes: 4 additions & 0 deletions src/classes/LittleBitsActionReportTrigger.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>34.0</apiVersion>
</ApexClass>
3 changes: 2 additions & 1 deletion src/classes/LittleBitsDeviceSubscriptionsSelector.cls
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public class LittleBitsDeviceSubscriptionsSelector extends fflib_SObjectSelector
LittleBitsDeviceSubscription__c.DeviceID__c,
LittleBitsDeviceSubscription__c.AccessToken__c,
LittleBitsDeviceSubscription__c.Event__c,
LittleBitsDeviceSubscription__c.FlowName__c
LittleBitsDeviceSubscription__c.FlowName__c,
LittleBitsDeviceSubscription__c.RunAsUser__c
};
}

Expand Down
8 changes: 7 additions & 1 deletion src/classes/LittleBitsReportTriggersSelector.cls
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ public class LittleBitsReportTriggersSelector extends fflib_SObjectSelector
LittleBitsReportTrigger__c.AggregateIndexForDuration__c,
LittleBitsReportTrigger__c.AggregateIndexForPercent__c,
LittleBitsReportTrigger__c.DeviceID__c,
LittleBitsReportTrigger__c.GroupingIndex__c
LittleBitsReportTrigger__c.GroupingIndex__c,
LittleBitsReportTrigger__c.UniqueName__c
};
}

Expand All @@ -54,6 +55,11 @@ public class LittleBitsReportTriggersSelector extends fflib_SObjectSelector
return (List<LittleBitsReportTrigger__c>) selectSObjectsById(idSet);
}

public List<LittleBitsReportTrigger__c> selectByUniqueName(Set<String> uniqueNames) {
return Database.query(
newQueryFactory().setCondition('UniqueName__c in :uniqueNames').toSOQL());
}

public Database.QueryLocator getAllActiveAsQueryLocator() {
return Database.getQueryLocator(
newQueryFactory().setCondition('Active__c = true').toSOQL());
Expand Down
75 changes: 63 additions & 12 deletions src/classes/LittleBitsService.cls
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,17 @@ global class LittleBitsService {
if(Trigger.isUpdate && Trigger.isAfter)
triggerHandler(Trigger.oldMap, Trigger.newMap);
}

/**
* Processes the given LittleBits Report Trigger records, runs the reports outputs to the devices
* Based on the great work by Cory Cowgil: http://corycowgill.blogspot.co.uk/2014/12/create-real-life-dashboard-with.html
**/
global static List<ReportTriggersResult> runReportTriggers(Set<String> uniqueNames) {
List<LittleBitsReportTrigger__c> reportTriggers =
new LittleBitsReportTriggersSelector().selectByUniqueName(uniqueNames);
Set<Id> reportTriggerIds = new Map<Id, LittleBitsReportTrigger__c>(reportTriggers).keySet();
return runReportTriggers(reportTriggerIds);
}

/**
* Processes the given LittleBits Report Trigger records, runs the reports outputs to the devices
Expand Down Expand Up @@ -269,21 +280,41 @@ global class LittleBitsService {
LittleBitsDeviceSubscription__c deviceSubscription = deviceSubscriptions[0];
flowParams.put('lbc_subscriptionRecordId', deviceSubscription.Id);

// Process the subscription and forward to a Flow
Type typeFactory = Type.forName('', FlowFactoryClass);
if(typeFactory==null) {
System.debug(FlowFactoryClass + ' class not found.');
return;
// Run as User implement via Inbound Email Handler
if(deviceSubscription.RunAsUser__c!=null) {
// Determine the Inbound Email address configured for this user
List<EmailServicesAddress> emailServices =
new EmailServicesAddressesSelector().selectByUser(new Set<Id> { deviceSubscription.RunAsUser__c });
if(emailServices.size()==0)
throw new LittleBitsServiceException('No Inbound Email Service found for user.');
if(emailServices.size()>1)
throw new LittleBitsServiceException('Multiple Inbound Email Services found for user.');
// Send the email
String emailServiceAddress = emailServices[0].LocalPart + '@' + emailServices[0].EmailDomainName;
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
mail.setToAddresses(new String[] { emailServiceAddress });
mail.setSubject(deviceSubscription.FlowName__c);
mail.setPlainTextBody(JSON.serialize(flowParams));
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}
else
{
// Invoke the flow as the running user
invokeFlow(deviceSubscription.FlowName__c, flowParams);
}
ILittleBitsFlowFactory flowFactory = (ILittleBitsFlowFactory) typeFactory.newInstance();
Flow.Interview flow = flowFactory.newInstance(deviceSubscription.FlowName__c, flowParams);
if(flow!=null)
flow.start();

// TODO: Think about some kind of logging mode or leave this purely to thd test mode i'm thinking about?
// ....
}

/**
* Process an event from a LittleBits device sent via Inbound Email (these are not received in bulk)
* Envelope contents is assumed to be JSON serialised String, Object map based on the tools standard Flow params
**/
global static void processSubscriptionEvent(Messaging.InboundEmail email) {
String flowName = email.subject;
Map<String, Object> flowParams = (Map<String, Object>)
JSON.deserializeUntyped(email.plainTextBody);
invokeFlow(flowName, flowParams);
}

@TestVisible
private static String FlowFactoryClass = 'lbc_LittleBitsFlowFactory';

Expand Down Expand Up @@ -320,6 +351,26 @@ global class LittleBitsService {

public class LittleBitsServiceException extends Exception {}

/**
* Dynamically invokes the given flow with the given params
**/
private static void invokeFlow(String flowName, Map<String, Object> flowParams) {

// Process the subscription and forward to a Flow
Type typeFactory = Type.forName('', FlowFactoryClass);
if(typeFactory==null) {
System.debug(FlowFactoryClass + ' class not found.');
return;
}
ILittleBitsFlowFactory flowFactory = (ILittleBitsFlowFactory) typeFactory.newInstance();
Flow.Interview flow = flowFactory.newInstance(flowName, flowParams);
if(flow!=null)
flow.start();

// TODO: Think about some kind of logging mode or leave this purely to thd test mode i'm thinking about?
// ....
}

/**
* Obtain an instance of the LittleBits Apex Device class (leverages Custom Setting config routes accordingly)
**/
Expand Down
38 changes: 38 additions & 0 deletions src/classes/LittleBitsServiceTest.cls
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ private class LittleBitsServiceTest {
reportTrigger.GroupingIndex__c = 0;
reportTrigger.AggregateIndexForPercent__c = 1;
reportTrigger.AggregateIndexForDuration__c = null;
reportTrigger.UniqueName__c = 'Test';
insert reportTrigger;
Opportunity opp = new Opportunity();
opp.Name = 'Test';
Expand All @@ -150,6 +151,42 @@ private class LittleBitsServiceTest {
System.assertEquals(true, mocksLittleBitsCloudAPI.calloutMade);
}

@IsTest(SeeAllData=true)
// Why SeeAllData? https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_analytics_test_reports.htm
private static void callRunReportTriggersEnsureACalloutOccursByUniqueName() {

// Create Mocks
MockLittleBitsOutputCloudAPI mocksLittleBitsCloudAPI = new MockLittleBitsOutputCloudAPI();
Test.setMock(HttpCalloutMock.class, mocksLittleBitsCloudAPI);

// Given
LittleBitsReportTrigger__c reportTrigger = new LittleBitsReportTrigger__c();
reportTrigger.Name = 'Test';
reportTrigger.ReportDeveloperName__c = 'ClosedWonOpportunities';
reportTrigger.Active__c = true;
reportTrigger.DeviceID__c = 'deviceId';
reportTrigger.AccessToken__c = 'accessToken';
reportTrigger.GroupingIndex__c = 0;
reportTrigger.AggregateIndexForPercent__c = 1;
reportTrigger.AggregateIndexForDuration__c = null;
reportTrigger.UniqueName__c = 'Test';
insert reportTrigger;
Opportunity opp = new Opportunity();
opp.Name = 'Test';
opp.StageName = 'Open';
opp.CloseDate = System.today();
opp.Amount = 10000;
insert opp;

// When
Test.startTest();
LittleBitsService.runReportTriggers(new Set<String> { reportTrigger.UniqueName__c });
Test.stopTest();

// Then
System.assertEquals(true, mocksLittleBitsCloudAPI.calloutMade);
}

@IsTest(SeeAllData=true)
// Why SeeAllData? https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_analytics_test_reports.htm
private static void callRunReportTriggersEnsureAExceptionOccurs() {
Expand All @@ -164,6 +201,7 @@ private class LittleBitsServiceTest {
reportTrigger.GroupingIndex__c = 0;
reportTrigger.AggregateIndexForPercent__c = 1;
reportTrigger.AggregateIndexForDuration__c = null;
reportTrigger.UniqueName__c = 'Test';
insert reportTrigger;

// When ... Then
Expand Down
33 changes: 33 additions & 0 deletions src/classes/LittleBitsSubscriberInboundEmailHandler.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright (c), Cory Cowgill and Andrew Fawcett
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the Cory Cowgill and Andrew Fawcett, nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**/

global with sharing class LittleBitsSubscriberInboundEmailHandler implements Messaging.InboundEmailHandler {
global Messaging.InboundEmailResult handleInboundEmail(Messaging.InboundEmail email, Messaging.InboundEnvelope envelope) {
Messaging.InboundEmailResult result = new Messaging.InboundEmailresult();
LittleBitsService.processSubscriptionEvent(email);
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>31.0</apiVersion>
</ApexClass>
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,12 @@
<field>FlowName__c</field>
</layoutItems>
</layoutColumns>
<layoutColumns/>
<layoutColumns>
<layoutItems>
<behavior>Edit</behavior>
<field>RunAsUser__c</field>
</layoutItems>
</layoutColumns>
<style>TwoColumnsLeftToRight</style>
</layoutSections>
<layoutSections>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
<behavior>Required</behavior>
<field>Name</field>
</layoutItems>
<layoutItems>
<behavior>Required</behavior>
<field>UniqueName__c</field>
</layoutItems>
</layoutColumns>
<layoutColumns>
<layoutItems>
Expand Down
Loading

0 comments on commit a743fff

Please sign in to comment.