Wednesday, August 31, 2011

Creating an APEX trigger in Salesforce.com

To start off the first day of DreamForce 2011 I am posting a "how to" on creating an APEX trigger in Salesforce.com.  Recently the business approached me with a need.  Currently we get leads from a vendor via the web-to-lead process.  This process works great but the business wanted a way for the leads that are created to show up in the associated agent's calendar.

In scoping out how to meet the need of the business I had to research what fields the vendor is populating and what fields are required and so forth.  First off, I determined that the best process to display an activity in the agent's calendar was to create a trigger on the lead object that can create an entry into the event object.  The fields that the vendor is populating that would provide value to this process are: FirstName, LastName, remote_Rep_Assigned__c (custom field that the vendor puts the name of the assigned agent and must match the name of a Salesforce user), LeadSource, Appointment_Date_Time__c (custom field that the vendor puts the event date/time in UTC time), and company.

Requirements:
- Setup the trigger to only create leads when the LeadSource has a specific value.
- Query the users object to match the provided name of the agent provided to by the vendor to a Salesforce user
- If a Salesforce user match is not found do not create an event
- The Appointment_Date_Time__c field is not required, if this field does not have a value do not create an event

This is a basic example of creating an APEX trigger which may be a good start in learning the basics for more advanced coding.

There are a few ways to go about coding, and testing your code.  My preferred method is to use the eclipse IDE with the force.com addin.  You can see instructions on the different methods currently available (http://wiki.developerforce.com/index.php/Force.com_IDE_Installation) (though in DreamForce today we saw the new web-based APEX code interface).  You will also either need to create a force.com developer account (free) or if you have an enterprise license create a sandbox for development.  You will not be able to code directly to your production site for security and performance purposes.

Parts of the instructions will be specific to eclipse though the Force IDE has a similar process.  Open eclipse, go to File->New->Project.  Open the Force.com folder and select "Force.com Project" and click next.  For the "Project Name" I entered "CreateEventFromLead".  You will need to know your user name, password and security token along with selecting the appropriate account type.  If your login information is correct you should get a screen to allow you which meta data objects to download (this is needed to help with progamming).  The default should be "APEX and VisualForce" leave this marked and click "Finish"  The interface will take a few moments to download the necessary meta data and setup the work space.

Once the desktop is created, you should see a new package in the "package explorer" tab called "CreateEventFromLead" (or what ever name you named it).  Open the folder called "src".  Right click on the "triggers" folder and select Force.com->New Apex Trigger.  You will see the trigger configuration window.  I gave the trigger the name of CreateEventFromLead, in the object drop down selected Lead and selected the "After Insert" option (we only want this trigger to trigger after a new lead is created).

You should now have a new sheet with the shell of the trigger statement in it.  Now starts the fun part.  One thing to note is that it is possible for a trigger to be processed for multiple leads at the same time.  This is very different if you are use to the normal RDMS environments where a trigger and ran for each insert statement.  In Salesforce for batch jobs a trigger could process multiple insert statements at the same time.  This is a performance and efficiency element.  Because of this we must setup the trigger to be able to process multiple items so a loop is needed.  We will start off with adding a "for" loop.  Our trigger statement should look similar to the following.
trigger CreateEventFromLead on Lead (after insert) {
 // Create a for loop to cycle through the number of leads inserted.
 //  Documention that I have come across says that typically only one record is
 //  inserted at a time except for bulk operations
 for (Lead lead : System.Trigger.new) {

 } // End of for (Lead lead : System.Trigger.new) statement
} // End of trigger CreateEventFromLead
Next I need to get the full name for the contact in the lead.  Because the FirstName field is not required I need to create a variable for this value and then perform some checks to get the info.  First I create a variable named "leadName" and then create an "if"/"else" structure to populate the variable based upon whether the FirstName field is populated.
 string leadName;
  // Check to see if the lead "name" value is null
  if(lead.FirstName == null) { // Check to see if the first Name value is null
   // If the First Name value is null the only put the last name
   //  into the variable
   leadName = lead.LastName;
  } else { // If the first and last name fields have a value then populate the variable with both values
   leadName = lead.FirstName + ' ' + lead.LastName;
  }
After this I needed to create a variable to store events records that we want to create so that we can insert them into the event object in Salesforce.
// Create a new variable to store events that need to be created
list<event> newEvents = new list<event>();
// Insert newEvents collection into salesforce.com
insert newEvents;
We now come to the first check.  We run a query to search for any user objects that match the agent name that the vendor provided in the Remote_Rep_Assigned__c field.  In between the variable declaration and the insert statement we will add the following code.

// Create a for loop to query salesforce to get a user record that matches the
  //  value in the Remote_Rep_Assigned field
  for (User user : [SELECT Id FROM User WHERE Name = :lead.Remote_Rep_Assigned__c LIMIT 1]) {
   // Create a variable to store the user Id value from the user record
   Id userId = user.Id;

   // If the userId variable has a value then continue with the code
   if(userId != null) {

   } // End of if(userId != null) statement
  } // End of for (User user... statement
We now come to the part to put in our business rules on when to create the event.  We should be to the part where we have a valid user.  Based upon the requirements from the business we only want create an event if the LeadSource has a certain value, for this exersize we will use the value of "Inbound".  We also have the rule to only create the event if there is a value in the Appointment_Date_Time__c.  Within the "If" structure to see if there is a userId we want to add the following.  First we create a boolean variable and set it to False.  We will use this variable while we go through our business rules to see if we want to create the event.
// Create a variable to set whether an event should be created based upon the collowing conditions
    boolean createEvent = false;

    // This is the condition logic to determine whether an event should be created
    //  If an additional conditions should be created use the following template
    //  Replace the pattern [value] with the value of the lead source that should be pulled into this process
    // ***********
    // else if(leadSource == '[value]') {
    //   createEvent = true;
    // }
    // ***********
    if(leadSource == 'Inbound') {
     // Set createEvent variable to true which will cause the event to be generated
     createEvent = true;
    } // End of if(leadSource == 'West') statement

    if(lead.Appointment_Date_Time__c == null) {
     createEvent = false;
    } // End of if(lead.Appointment_Date_Time__c == null) statement
If our lead object passes all the business rule checks then we want to create the event.  First we put in an "if" structure to check our variable to see if the process should create the event.  If the checks are valid then we create a new event and assign some values.  I also added a try/catch structure just to make sure we do not get any errors (though we should have enough checks in place to not need this it is good to be on the safe side.
// If one of the conditions set the createEvent variable to True then execute the code to create the event
    if(createEvent) {
     // With the newEvents variable that was setup to store new events
     //  We call the "add" function. This function takes a parameter for an event variable
     //  We create a new event variable within the add function call.  The new
     //  Event variable creation process takes parameters to populate certain values with values
     //  that we pass to it.
     try {
      newEvents.add(new Event(
       // Pass the Id value for the user that matched what the third party sent to the owner Id
       //  This will assign the event to the user so that it shows up on their calendar in salesforce.com
       OwnerId = userId,
       // Pass the appointment datetime value passed from the third party in the lead
       //  to the event's date/time value
       ActivityDateTime = lead.Appointment_Date_Time__c,
       // Pass the Id of the lead to the WhoId value for the event.  This will assign the event
       //  to the lead so that it shows up in the lead view as well as allowing the lead info to be
       //  visible in the event details
       WhoId = lead.Id,
       // Passes the duration value of the event.  The third party does not pass a value
       //  so we are defaulting this value to 60 minutes
       DurationInMinutes = 60,
       // Passes a custom value into the subject line of the event to provide needed details
       //  for the owner of the event
       Subject = 'Call ' + leadName + ' with ' + lead.Company
       ) // End of new event
      ); // End of newEvent add funciton
     }
     catch(Exception e){
      system.debug(e + ' user id:' + userId + ' DateTime:' + lead.Appointment_Date_Time__c + ' lead Id:' + lead.Id);
     }
    } // End of if(CreateEvent) statement

This the completion of the trigger code.  Now comes the pain.  In order to be able to prepare the code for production we have to create test cases the cover at least 75% of the code.  There is a lot of information out on the web on how to do this but I still had a hard time with it.  I did a lot of testing and playing around until I was able to come up with test cases that would work.  In fact I spent more time on the test cases than I did on the actual code.

First, you right click on the "classes" and select Force.com->New APEX Class.  Give it a name, I called it ConvertEventFromLeadTest and gave it the template "Test Class".  This will create the class shell and include the @isTest assignment to the class.  The purpose of this test class is to pass values to the trigger to make sure that everything test out.  You should try to make it as dynamic as possible so that it can fit any scenario.  This class has the comments in the code sufficient to explain what is going on.  The big element was creating a loop for a controlled number of test records that would force the LeadSource to be the one we want in some situations and under others to pick one of the other defined LeadSources.
@isTest
private class CreateEventFromLeadTest {
    static testMethod void myUnitTest() {
        // Perform our data preparation.
     List<lead> leads = new List<lead>{};
     List<schema.picklistentry> leadSources = Lead.LeadSource.getDescribe().getPicklistValues();
     User user = [SELECT Id, Name FROM User WHERE IsActive = true LIMIT 1];
     string userName = user.Name;
    
     Integer iPicklist = 0;
    
     for(Schema.Picklistentry pick : leadSources) {
      iPicklist++;
     }
    
     Integer leadSourceCounter = 0;
     for(Integer i = 0; i < 60; i++){
      Integer days = Math.round(Math.random()*10.0);
    
      date dt;
      date tday = date.today();
    
         Lead l = new Lead();
         if(i<10) {
          l.FirstName = 'test';
          l.LastName = 'lead ' + i;
          l.LeadSource = 'Inbound';
         }
         else {
          l.LastName = 'test lead ' + i;
          l.LeadSource = leadSources[leadSourceCounter].getLabel();
         }
         Integer remainder = math.mod(i, 2);
         if(remainder==0) {
          dt = tday.addDays(days);
         }
         else {
          dt = null;
         }
         l.Email = 'test' + i + '@test.com';
        
         if(leadSourceCounter > iPicklist-1) {
          leadSourceCounter = 0;
         }
         l.Remote_Rep_Assigned__c = userName;
         l.Appointment_Date_Time__c = dt;
         l.Company = 'test company ' + i;
         l.Description = 'This Lead is probably left over from testing. It should probably be deleted.';
         leads.add(l);
        
       leadSourceCounter++;
     }

     // Start the test, this changes governor limit context to
     // that of trigger rather than test.
     test.startTest();
 
     // Insert the Lead records that cause the trigger to execute.
     insert leads;
 
     // Stop the test, this changes limit context back to test from trigger.
     test.stopTest();
 
     // Query the database for the newly inserted records.
     List<lead> insertedLeads = [SELECT FirstName, LastName, Description
                                       FROM Lead
                                       WHERE Id IN :leads];
 
     // Assert that the Description fields contains the proper value now.
     for(Lead l : insertedLeads){
       System.assertEquals(
         'This Lead is probably left over from testing. It should probably be deleted.',
         l.Description);
     }
 
     // Query the database for the newly inserted records.
     List<event> insertedEvents = [SELECT Subject
                                       FROM Event
                                       WHERE WhoId IN :leads];
 
     // Assert that the Description fields contains the proper value now.
     for(Event e : insertedEvents){
       System.assert(true);
     }
    }
}
Once the test class is done, you can right click on the class and select Force.com->Run Tests.  This will run the test cases against the trigger and provide all the results.  If everything is successful you are now ready to package the code for production.

2 comments:

  1. Write more, thats all I have to say. Literally, it
    seems as though you relied on the video to make your point.
    You clearly know what youre talking about, why waste your
    intelligence on just posting videos to your weblog when
    you could be giving us something informative to read?

    my webpage :: adobe

    ReplyDelete
  2. I will do a new write-up to add more information based upon some best practices that we have learned since this was written.

    ReplyDelete