Search

Friday 11 August 2017

A quick note to warn anyone that is planning to use the Salesforce Marketing Cloud and the affect on your data storage costs.

If you are setting up Marketing cloud watch out the affect it can have on data storage costs if you use Marketing Cloud connect. Marketing Cloud connect allows you to use data from Sales and Service cloud, however it also can store huge amounts of data on the Sales and Service cloud when you run your campaigns. You should look at configuring the Marketing cloud to only store aggregated email data in the Sales and Service cloud otherwise you may cause the customer to hit their data limits.

Monday 26 December 2016

Tuesday 31 May 2016

Useful Summer 16 Features


Cloning Sandboxes


You can now create a sandbox by cloning an existing sandbox rather than using your production org as your source. Save time by populating any type of sandbox with a previously chosen set of data and metadata. (Sandbox templates, which serve a similar purpose, are available only for Full or Partial Copy Sandboxes.) Sandbox-to-sandbox cloning facilitates iterative development, allowing you to freeze development in one sandbox and pick up where you left off in a new one. This feature is available in both Lightning Experience and Salesforce Classic.



Simplify Development of Asynchronous Callouts by Using Named Credentials


You can now use named credentials in long-running asynchronous callouts from a Visualforce page’s controller. A named credential specifies the URL of a callout endpoint and its required authentication parameters in one definition. Salesforce manages all authentication for callouts that specify a named credential as the callout endpoint so that your Apex code doesn’t have to. You can also skip remote site settings, which are otherwise required for callouts to external sites, for the site defined in the named credential. This feature is available in both Lightning Experience and Salesforce Classic.




Get a Map of Populated SObject Fields


We’ve added a method on the Apex SObject class that makes it more efficient to iterate over fields that have been populated in memory. Previously, iterating over just the populated the fields of an SObject involved some complicated programming. For example, if you queried an SObject using SOQL, it wasn’t easy to tell which fields were returned. In Summer ’16, we’ve introduced a new method on the SObject class that returns a map of populated field names and their corresponding values:

Account a = new Account();
a.name = 'TestMapAccount1';
insert a;
a = [select Id,Name from Account where id=:a.Id];
Map fieldsToValue = a.getPopulatedFieldsAsMap();

for (String fieldName : fieldsToValue.keySet()){
    System.debug('field name is ' + fieldName + ', value is '
    + fieldsToValue.get(fieldName));
}

Admittedly the example by Salesforce above isn’t amazing but if the SOQL was a dynamic SOQL I can see a lot of benefit in this

You can also perform without a SOQL

Account a = new Account();
a.name = 'TestMapAccount1';
insert a;

Map fieldsToValue = a.getPopulatedFieldsAsMap();
for (String fieldName : fieldsToValue.keySet()){
   System.debug('field name is ' + fieldName + ', value is ' +      
   fieldsToValue.get(fieldName));
}



EmailTemplate Functions


RenderEmailTemplateBodyResult Class

getMergedBody() Returns the rendered body text with merge field references replaced with the corresponding record data.



System.Messaging Class

renderEmailTemplate(whoId, whatId, bodies)
Returns an array of RenderEmailTemplateBodyResult objects, each of which corresponds to an element in the
supplied array of text bodies. Each RenderEmailTemplateBodyResult provides a success or failure indication
along with either an error code or the rendered text.

renderStoredEmailTemplate(templateId, whoId, whatId)
Renders a text, custom, HTML, or Visualforce email template that exists in the database into an instance of
Messaging.SingleEmailMessage.




$Resource Global Value Provider And Lightning


The $Resource global value provider lets you reference images, style sheets, and JavaScript code you’ve uploaded in static resources. Using $Resource lets you reference assets by name, without worrying about the gory details of URLs or file paths. You can use $Resource in Lightning components markup and within JavaScript controller and helper code. To reference a specific resource in component markup, use $Resource.resourceName within an expression. Include CSS style sheets or JavaScript libraries into a component using the tag. To obtain a reference to a static resource in JavaScript code, use $A.get('$Resource.resourceName').




Saturday 21 May 2016

Get a Map of Populated SObject Fields

We’ve added a method on the Apex SObject class that makes it more efficient to iterate over fields that have been populated in memory. Previously, iterating over just the populated the fields of an SObject involved some complicated programming. For example, if you queried an SObject using SOQL, it wasn’t easy to tell which fields were returned. In Summer ’16, we’ve introduced a new method on the SObject class that returns a map of populated field names and their corresponding values:


      Account a = new Account();
      a.name = 'TestMapAccount1';
      insert a;
      a = [select Id,Name from Account where id=:a.Id];
      Map<String, Account> fieldsToValue = a.getPopulatedFieldsAsMap();

      for (String fieldName : fieldsToValue.keySet()){
         System.debug('field name is ' + fieldName + ', value is ' +          
         fieldsToValue.get(fieldName));
      }


Admittedly the example by Salesforce above isn’t amazing but if the SOQL was a dynamic SOQL I can see a lot of benefit in this
You can also perform without a SOQL
 ld names and their corresponding values:


      Account a = new Account();
      a.name = 'TestMapAccount1';
      insert a;
     
      Map<String, Account> fieldsToValue = a.getPopulatedFieldsAsMap();

      for (String fieldName : fieldsToValue.keySet()){
         System.debug('field name is ' + fieldName + ', value is ' +          
         fieldsToValue.get(fieldName));
      }




Sunday 20 March 2016

Salesforce PageBlockSections Styling Problems

When you want to render displays of parts of a PageBlockSection because PageBlockSections and PageBlockSectionItems do not take the attribute Rendered you have to use Outputpanels to turn on and off the display of the PageBlockSection.

The problem with this is that the Outputpanel interferes with the styling of the PageBlockSection and the PageBlockSection does not display properly.

This can be seen in this example
( you will need to create a controller class with the visualforce page ):

The aim of this visualforce page is to simply display a button and when the button is pressed the entire Outputpanel is displayed.



<apex:form>
      <apex:pageBlock >
            <apex:outputPanel id="Results">
                  <apex:pageBlockSection title="" columns="1" collapsible="false">                   
                 <apex:outputPanel rendered="{!displaySelectedFrame}"  >
                              <apex:pageBlockSectionItem >
                                    <apex:outputPanel >
                                          <apex:outputLabel value="A Label" />
                                          <h1>{! frameworkName}</h1>
                                    </apex:outputPanel>
                              </apex:pageBlockSectionItem>

                              <apex:pageBlockSectionItem >
                                    <apex:outputPanel >
                                          <apex:outputLabel value="Description" />
                                          {! description}
                                    </apex:outputPanel>
                              </apex:pageBlockSectionItem>
                 </apex:outputPanel>
                              <apex:pageBlockSectionItem >
                                    <apex:commandButton value="Display Section" action="{!doSomething}" rerender="Results"  />
                              </apex:pageBlockSectionItem>
                  </apex:pageBlockSection>
                  </apex:outputPanel>
            </apex:pageBlock>
      </apex:form>





If you move the rendered to inside the PageBlockSectionItems it solves the styling issue but the entire section of the intended rendered area no longer collapses the PageBlockSectionItems so when the page starts there is a large white space created by the PageBlockSectionItems and so the styling is still not correct. This is shown in the following:



<apex:form>
      <apex:pageBlock >
            <apex:outputPanel id="Results">
                  <apex:pageBlockSection title="" columns="1" collapsible="false">                   
                       
                              <apex:pageBlockSectionItem >
                                    <apex:outputPanel rendered="{!displaySelectedFrame}">
                                          <apex:outputLabel value="A Label" />
                                          <h1>{! frameworkName}</h1>
                                    </apex:outputPanel>
                              </apex:pageBlockSectionItem>

                              <apex:pageBlockSectionItem >
                                    <apex:outputPanel rendered="{!displaySelectedFrame}" >
                                          <apex:outputLabel value="Description" />
                                          {! description}
                                    </apex:outputPanel>
                              </apex:pageBlockSectionItem>
                              <apex:pageBlockSectionItem >
                                    <apex:commandButton value="Display Section" action="{!doSomething}"                                           rerender="Results"  />
                              </apex:pageBlockSectionItem>
                  </apex:pageBlockSection>
                  </apex:outputPanel>
            </apex:pageBlock>
      </apex:form>




  
To solve both situations use a PageBlockSection inside the Outputpanel that renders the section that we are wanting to turn on/off the display. By having 2 PageBlockSections the problem is solved.




<apex:form>
      <apex:pageBlock >
            <apex:outputPanel id="Results">
                  <apex:pageBlockSection columns="1" collapsible="false">                      
                        <apex:outputPanel layout="block" rendered="{!displaySelectedFrame}"  >
                      <apex:pageBlockSection columns="1" collapsible="false">
                                    <apex:pageBlockSectionItem >
                                          <apex:outputPanel >
                                                <h1><apex:outputLabel value="A Label" /></h1><br/>
                                                {! frameworkName}
                                          </apex:outputPanel>
                                    </apex:pageBlockSectionItem>

                                    <apex:pageBlockSectionItem >
                                          <apex:outputPanel >
                                                <h1><apex:outputLabel value="Description" /></h1><br/>
                                                {! description}
                                          </apex:outputPanel>
                                    </apex:pageBlockSectionItem>
                      </apex:pageBlockSection>
                        </apex:outputPanel>
                              <apex:pageBlockSectionItem >
                                    <apex:commandButton value="Display Section" action="{!doSomething}" rerender="Results"  />
                              </apex:pageBlockSectionItem>
                  </apex:pageBlockSection>
                  </apex:outputPanel>
            </apex:pageBlock>
      </apex:form>




                                       

Saturday 5 March 2016

Trigger Pattern Framework - session 2





In my last blog I introduced my Trigger Pattern Framework, now I will cover Trigger Control, which is one of the main improvements to other well documented frameworks.

DMLs are very processor intensive and conserving this precious resource in a multitenant environment is ever more so important.
So I built into my framework a capability using a Custom Setting to activate / deactivate per trigger for any User, Profile, or the entire Organization; or with a separate Custom Setting to be able to activate / deactivate ALL triggers for any User, Profile, or the entire Organization. This was great in situations where companies require migrating data from 1 system to another and you want to safeguard that no unwanted actions occur through the execution of code in the trigger.


However there are also many situations such as in unit tests where we create test data, to avoid running through all the code in triggers which is not necessary unless we are testing specifically the triggers to disable the triggers using the Custom Settings above will require running a DML and since we are trying to avoid DMLs because they are expensive to run we need another mechanism to bypass the code in the trigger, therefore we introduce a static variable to do this work. Here is a section in the TriggerFactory class that controls the execution using these methods and also the code from the calling classes that are used in this part of the framework.


boolean notriggerSetting;
                 boolean noTriggersPerObject;
                 try{
                      notriggerSetting = TriggerController.globalTriggerControlSetting();   
                      noTriggersPerObject = TriggerController.globalTriggerPerObjectControlSetting(objType);   
                 }
                 catch (Exception ex){system.debug('error in trigger controller ' + ex); }

                Type soType = Type.forName(objType);
               
    if (!notriggerSetting && !noTriggersPerObject && !TriggerController.getTriggerControlValue(soType, TriggerController.TRIGGER_ALL)) {  
                               
                                if (Trigger.isBefore){
                                                if (Trigger.isUpdate && !TriggerController.getTriggerControlValue(soType, TriggerController.TRIGGER_UPDATE)){
                                                                                handler.beforeUpdate(Trigger.oldmap, Trigger.newmap);               
                                                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(soType, TriggerController.TRIGGER_UPDATE), true);                                
                                                }
                                                else if (Trigger.isDelete && !TriggerController.getTriggerControlValue(soType, TriggerController.TRIGGER_DELETE)){
                                                                                handler.beforeDelete(Trigger.oldmap);
                                                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(soType, TriggerController.TRIGGER_DELETE), true);
                                                }
                                                else if (Trigger.isInsert && !TriggerController.getTriggerControlValue(soType, TriggerController.TRIGGER_INSERT)){
                                                                                handler.beforeInsert(Trigger.newmap);                                                         
                                                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(soType, TriggerController.TRIGGER_INSERT), true);
                                                }
                                                else if (Trigger.isUnDelete && !TriggerController.getTriggerControlValue(soType, TriggerController.TRIGGER_UNDELETE)){
                                                                                handler.beforeUnDelete(Trigger.oldmap);
                                                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(soType, TriggerController.TRIGGER_UNDELETE), true);          
                                                }
                                                 
                                }
                                else{
                                               
                                                if (Trigger.isUpdate && !TriggerController.getTriggerControlValue(soType, TriggerController.TRIGGER_UPDATE)){
                                                                                handler.afterUpdate(Trigger.oldmap, Trigger.newmap);                                                               
                                                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(soType, TriggerController.TRIGGER_UPDATE), true);
                                                }
                                                else if (Trigger.isDelete && !TriggerController.getTriggerControlValue(soType, TriggerController.TRIGGER_DELETE)){
                                                                                handler.afterDelete(Trigger.oldmap);
                                                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(soType, TriggerController.TRIGGER_DELETE), true);
                                                }
                                                else if (Trigger.isInsert && !TriggerController.getTriggerControlValue(soType, TriggerController.TRIGGER_INSERT)){
                                                                                handler.afterInsert(Trigger.newmap);                                                            
                                                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(soType, TriggerController.TRIGGER_INSERT), true);
                                                }
                                                else if (Trigger.isUnDelete && !TriggerController.getTriggerControlValue(soType, TriggerController.TRIGGER_UNDELETE)){
                                                                                handler.afterUnDelete(Trigger.oldmap, Trigger.newmap);
                                                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(soType, TriggerController.TRIGGER_UNDELETE), true);
                                                }
                                }
                               
    }




public class TriggerController {

                public static map<TriggerControlKeyValue, boolean> triggerDisableMap = new map<TriggerControlKeyValue, boolean>();
                public static map<TriggerControlKeyValue, boolean> triggerSuccessMap = new map<TriggerControlKeyValue, boolean>();
               
                public static final String TRIGGER_ALL = 'ALL';
                public static final String TRIGGER_INSERT = 'INSERT';
                public static final String TRIGGER_UPDATE = 'UPDATE';
                public static final String TRIGGER_DELETE = 'DELETE';
                public static final String TRIGGER_UNDELETE = 'UNDELETE';
               
                public static Boolean getTriggerControlValue(System.Type objType, String triggerType){
                                TriggerControlKeyValue tkv = new TriggerControlKeyValue(objType ,triggerType);
                                Boolean triggerDisable = false;
                                if (triggerDisableMap != null && triggerDisableMap.containskey(tkv))
                                                triggerDisable = triggerDisableMap.get(tkv);
                                               
                                return triggerDisable;
                }

                public static void setTriggerControlValue(System.Type objType, String triggerType, Boolean triggerDisable){
                                TriggerControlKeyValue tkv = new TriggerControlKeyValue(objType ,triggerType);
                                                               
                                for (TriggerControlKeyValue eachtk : triggerDisableMap.keyset()){
                                                if (eachtk == tkv){
                                                                tkv = eachtk;                                                                       
                                                                break;
                                                }
                                }
                                triggerDisableMap.put(tkv, triggerDisable);                   
                }
               
                public static Boolean getTriggerSuccessValue(System.Type objType, String triggerType){
                                TriggerControlKeyValue tkv = new TriggerControlKeyValue(objType ,triggerType);
                                Boolean triggerSuccess = false;
                               
                                for (TriggerControlKeyValue eachtk : triggerSuccessMap.keyset()){
                                                if (eachtk == tkv){
                                                                triggerSuccess = triggerSuccessMap.get(eachtk);
                                                                break;
                                                }
                                }
                               
                                return triggerSuccess;
                }
               
               
public static boolean globalTriggerControlSetting(){
               
                return (((Triggers_Off__c.getOrgDefaults() != null) ? Triggers_Off__c.getOrgDefaults().value__c : false) || Triggers_Off__c.getInstance(UserInfo.getUserId()).value__c  || Triggers_Off__c.getInstance(UserInfo.getProfileId()).value__c) ;
}

public static boolean globalTriggerPerObjectControlSetting(String obj){
               
                if (obj != null && obj != '') {
                                if (!obj.endswith('__c')) obj += '__c';
                                                boolean s = false;
                                                if (Trigger_Per_Object__c.getOrgDefaults() != null) s =  (boolean)Trigger_Per_Object__c.getOrgDefaults().get(obj);

                                                boolean t = false;
                                                if (Trigger_Per_Object__c.getInstance(UserInfo.getUserId()) != null) t =  (boolean)Trigger_Per_Object__c.getInstance(UserInfo.getUserId()).get(obj);

                                                boolean u = false;
                                                if (Trigger_Per_Object__c.getInstance(UserInfo.getProfileId()) != null) u =  (boolean)Trigger_Per_Object__c.getInstance(UserInfo.getProfileId()).get(obj);

                                               
                                                if  (s == null) s = false;
                                                if  (t == null) t = false;
                                                if  (u == null) u = false;
                                               
                                                return (s || t || u);
                }else
                                return false;
}

}




public class TriggerControlKeyValue {
               
public system.type objectType;
public string triggerType;

                public TriggerControlKeyValue(system.type thisObjectType, string thisTriggerType) {
                                objectType = thisObjectType;
                                triggerType = thisTriggerType;
                }

               
                public boolean equals(object obj){
                               
                                if (obj instanceof TriggerControlKeyValue){
                                                TriggerControlKeyValue t = (TriggerControlKeyValue)obj;
                                                return (objectType.equals(t.objectType) && triggerType.equals(t.triggerType));
                                }
                                return false;
                }
               
                public integer hashCode(){
                                return system.hashCode(objectType) * system.hashCode(triggerType);
                }

}



We also need to be able to unit test the framework to test the trigger control has been built correctly and remains operational. To test this part of the framework we don’t want to test the outcomes from running each individual part of the trigger as the outcomes will be different per trigger, instead we just need to test that the code passed through the track of code we expect. For this purpose, another map is used.


@istest
public class TriggerControllerTest {
                public static TestDataCreation td = new TestDataCreation();
                public static Account acc;
                public static Triggers_Off__c trig;
                public static Trigger_Per_Object__c trigPerObject;
               
                static{
                                acc = td.insertAccount(null);
                }
               
                static testMethod void AccountTriggerGlobalCSTest() {
                                //test global CS on/off
                                trig = td.insertTriggersOff(null);
                                Test.startTest();
                                                //record should be inserted                                
                                                //system.assert([Select id From Account where Name=:defaultCusName].size() == 1);
                                                system.assert(TriggerController.getTriggerSuccessValue(Account.class,TriggerController.TRIGGER_INSERT) == true);
                                               
                                                //should change
                                                acc.Name = 'ChangeCusName';
                                                update acc;                                          
                                                system.assert(TriggerController.getTriggerSuccessValue(Account.class,TriggerController.TRIGGER_UPDATE) == true);

                                                //reset                                    
                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(Account.class, TriggerController.TRIGGER_UPDATE), false);
                                               
                                                trig.value__c = true;
                                                update trig;
                                               
                                                //should not change
                                                acc.Name = 'DefaultCusName';
                                                update acc;                                          
                                                system.assert(TriggerController.getTriggerSuccessValue(Account.class,TriggerController.TRIGGER_UPDATE) == false);
                                               
                                                //disable insert
                                               
                                test.stopTest();
                }

                static testMethod void AccountTriggerPerObjectCSStaticTest() {
                                //test trigger control using Per Object CS
                                trigPerObject = td.insertTriggersPerObject(null);
                                Test.startTest();
                                                //reset
                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(Account.class, TriggerController.TRIGGER_INSERT), true);
                                               
                                                //record should be inserted but shouldnt set Account_Insert_Succeeded
                                                trigPerObject.Account__c = true;
                                                update trigPerObject;
                                               
                                                //reset                                    
                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(Account.class, TriggerController.TRIGGER_INSERT), false);
                                               
                                                system.assert([Select id From Account].size() == 1);
                                               
                                                Account acc2 = td.insertAccount(null);
               
                                                system.assert([Select id From Account].size() == 2);
                                                system.assert(TriggerController.getTriggerSuccessValue(Account.class,TriggerController.TRIGGER_INSERT) == false);

                                test.stopTest();
                }             

                static testMethod void AccountTriggerGlobalStaticTest() {
                                //test trigger control using static variables
                                Test.startTest();
                                                //disable update
                                                TriggerController.triggerDisableMap.put(new TriggerControlKeyValue(Account.class, TriggerController.TRIGGER_ALL), true);
                                               
                                                acc.Name = 'ChangeCusName';
                                                update acc;                                          
                                                system.assert(TriggerController.getTriggerSuccessValue(Account.class,TriggerController.TRIGGER_UPDATE) == false);

                                                //reset
                                                TriggerController.triggerDisableMap.put(new TriggerControlKeyValue(Account.class, TriggerController.TRIGGER_ALL), false);
                                                TriggerController.triggerDisableMap.put(new TriggerControlKeyValue(Account.class, TriggerController.TRIGGER_UPDATE), true);
                                               
                                                acc.Name = 'DefaultCusName';
                                                update acc;                                          
                                               
                                                //should not change
                                                system.assert(TriggerController.getTriggerSuccessValue(Account.class,TriggerController.TRIGGER_UPDATE) == false);
                                               
                                                //update should run
                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(Account.class, TriggerController.TRIGGER_UPDATE), false);
                                                TriggerController.triggerDisableMap.put(new TriggerControlKeyValue(Account.class, TriggerController.TRIGGER_UPDATE), false);
                                                acc.Name = 'DefaultCusName';
                                                update acc;
                                                system.debug('## TriggerController ' + TriggerController.triggerSuccessMap);                                       
                                                system.assert(TriggerController.getTriggerSuccessValue(Account.class,TriggerController.TRIGGER_UPDATE) == true);
                                               
                                                //test insert trigger code off
                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(Account.class, TriggerController.TRIGGER_INSERT), false);
                                                TriggerController.triggerDisableMap.put(new TriggerControlKeyValue(Account.class, TriggerController.TRIGGER_INSERT), true);
                                                system.assert([Select id From Account].size() == 1);
                                               
                                                Account acc2 = td.insertAccount(null);
               
                                                system.assert([Select id From Account].size() == 2);
                                                //should not change
                                                system.assert(TriggerController.getTriggerSuccessValue(Account.class,TriggerController.TRIGGER_INSERT) == false);
                                               
                                test.stopTest();
                }

}