Python – Read from CSV file

Here’s what you can use to read from a CSV file, row by row:

import csv
import tempfile, os

def release_script():
    file_path = os.path.join(tempfile.gettempdir(), 'customer_since.csv')
    print file_path # Refers to %temp% on Windows and /tmp/ on Linux
    csv_file = open(file_path, 'rb')
    reader = csv.DictReader(csv_file)
    for row in reader:
        sfid = row['Account Number']
        customer_since = row['Customer Since']
        print "%s : %s" % (sfid, customer_since,)
Advertisements

Salesfoce Apex Class to send alerts for open Leads using Batch Apex and Scheduler

The use case for this exercise is to send email notification to lead owners when the lead/deal hasn’t been approved/rejected within 48 hours. It’s to ensure that there are no open deals that are sitting in the system without being attended to.

Now, this can be done using a Time Based Workflow in Salesforce, but with time based workflows, the records get locked. So users won’t be able to act upon them while they are locked. Will cover more details on that later.

But given the power of Apex, we can easily create a custom class that could solve the problem. The class would need to implement 2 interfaces and let’s discuss about them individually first:
1. Batch Apex using Database.Batchable
Batch Apex is exposed as an interface that must be implemented by the developer. Batch jobs can be programmatically invoked at runtime using Apex class that is implemented.
Need for Batch Apex: You may be familiar about the Salesforce governor limits on its data. When you want to fetch thousands of records or fire DML on thousands of rows on objects it is very complex in Salesforce and it does not allow you to operate on more than certain number of records which satisfies the Governor limits. But for medium to large enterprises, it is essential to manage thousands of records every day. Adding/editing/deleting them when needed. Salesforce has come up with a powerful concept called Batch Apex. Batch Apex allows you to handle more number of records and manipulate them by using a specific syntax. We have to create an global apex class which extends Database.Batchable Interface because of which the salesforce compiler will know, this class incorporates batch jobs.

Here’s how Batch Apex works under the hood. Let’s say you want to process 1 million records using Batch Apex. The execution logic of the batch class is called once for each batch of records you are processing. Each time you invoke a batch class, the job is placed on the Apex job queue and is executed as a discrete transaction. This functionality has two awesome advantages:

  • Every transaction starts with a new set of governor limits, making it easier to ensure that your code stays within the governor execution limits.
  • If one batch fails to process successfully, all other successful batch transactions aren’t rolled back.

The Database.Batchable interface contains three methods that must be implemented.
a. start() method:
Used to collect the records or objects to be passed to the interface method execute for processing. This method is called once at the beginning of a Batch Apex job and returns either a Database.QueryLocator object or an Iterable that contains the records or objects passed to the job.
Most of the time a QueryLocator does the trick with a simple SOQL query to generate the scope of objects in the batch job. But if you need to do something crazy like loop through the results of an API call or pre-process records before being passed to the execute method, you might want to check out the Custom Iterators section.
With the QueryLocator object, the governor limit for the total number of records retrieved by SOQL queries is bypassed and you can query up to 50 million records. However, with an Iterable, the governor limit for the total number of records retrieved by SOQL queries is still enforced.

global (Database.QueryLocator | Iterable) start(Database.BatchableContext bc) {
    // collect the batches of records or objects to be passed to execute
}

b. execute() method:
Performs the actual processing for each chunk or “batch” of data passed to the method. The default batch size is 200 records. Batches of records are not guaranteed to execute in the order they are received from the start method.

global void execute(Database.BatchableContext bc, List

records){ // process each batch of records }

c. finish() method:
Used to execute post-processing operations (for example, sending a summary email) and is called once after all batches are processed.

global void finish(Database.BatchableContext bc){
    // execute any post-processing operations
}

1. Scheduling using Schedulable
The interface doesn’t need much description. The class that needs to be scheduled for execution must implement the only method under Schedulable interface
a. execute() method:

global void execute(SchedulableContext ctx) {
    // execute code that is scheduled
}

Now, let’s get back to the original use case – Notify owner when a lead is open for more than 48 hours. Let’s first implement an Apex class that implements Database.Batchable and Schedulable

global class PendingDealNotification implements Database.Batchable, Schedulable{
    
    global Database.QueryLocator start(Database.BatchableContext batchableContext) { 
        String query = 'SELECT Id, OwnerID FROM Lead WHERE CreatedDate = LAST_N_DAYS:2 AND owner_notified__c= False';
        return Database.getQueryLocator(query);
    }
    
    global void execute(Database.BatchableContext batchableContext, List leadList){
        try{
            emailOwners(leadList);
            //update a flag in lead to indicate mail sent
            for(Lead lead : leadList){
                lead.owner_notified__c = True;
            }
            update leadList;
        }catch(Exception e){
            System.debug('Exception :: ' + e.getMessage());
        }
        
    }
    
    global void finish(Database.BatchableContext batchableContext) { 
        
    }
    
    global void execute(SchedulableContext schedulableContext) {
         Database.executeBatch(new PendingDealNotification());   
    }
    
    private void emailOwners(List leadList){
        
        List mailList = new List();
        User userToNotify = [Select Id FROM User WHERE Name = 'Varun Verma'];
        EmailTemplate templateId = [Select Id from EmailTemplate where DeveloperName  = 'Request_for_Deal_Registration_Approval'];
        for(Lead lead : leadList){
            Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
            mail.setTargetObjectId(lead.id);
            mail.setTreatTargetObjectAsRecipient(false);
            mail.setToAddresses(new List{userToNotify.id});
            mail.setTemplateID(templateId.Id);
            mail.setSaveAsActivity(false);
            mailList.add(mail);
        }
        if(mailList != null){
            Messaging.sendEmail(mailList);
        } 
    }
}

Now, as you may notice, this requires having a custom field under the Lead object (owner_notified__c) and a custom Email Template (which you can create by yourself as it doesn’t have any link to the data or this Apex class).

To execute this from the developer console, navigate to PLATFORM TOOLS > Custom Code > Apex Test Execution > Developer Console > Debug > Open Execute Anonymous Window. Type in the following code and hit Execute.

PendingDealNotification p = new PendingDealNotification();
p.execute(null);

Once you’ve tested out the class execution using the developer console, you can go ahead and create a scheduled execution plan for this class under PLATFORM TOOLS > Custom Code > Apex Classes > Schedule Apex.

PHP – LDAP Authentication and Getting LDAP User Groups for user

Some pretty useful functions to check if LDAP Username/Password entered are valid. And to get LDAP Groups that a user belongs to so group based access can be added.

$host = 'ldap-server.domain.com';
$port = '636';
$protocol = 'ldaps';
$base_dn = 'ou=corp,dc=domain,dc=pvt';
$domain = "@domain.pvt";
                
function validAdminUser($uname) {
    // Returns true if user belongs in group support/it/systems
    $validAdminUser = false;
    $arr_usr_groups = get_groups($uname);
    foreach($arr_usr_groups as $group) {
        if(strpos(strtolower($group), "cn=support,") !== false ||
           strpos(strtolower($group), "cn=it,") !== false ||
           strpos(strtolower($group), "cn=systems,") !== false) {
            $validAdminUser = true;
            break;
        }
    }
    return $validAdminUser;
}

function ldap_login($username, $password) {
    global $host, $port, $protocol, $base_dn, $domain;
    if ($username && $password) {
        $connection_string = "$protocol://$host:$port";
        $conn = @ldap_connect($connection_string) or $msg = "Could not connect: $connection_string";
        ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3);
        ldap_set_option($conn, LDAP_OPT_REFERRALS, 0);
    
        $ldaprdn = $username.$domain;
        $ldapbind = @ldap_bind($conn, $ldaprdn, $password);
        if ($ldapbind) {
            $search = ldap_search($conn, $base_dn, "(samaccountname=$username)");
            if ($search) {
                $result = ldap_get_entries($conn, $search);
                if ($result['count'] > 0) {
                    $returnval = 1; // "Success"
                }
                else {
                    $returnval = -1; // "User not found"
                }
            }
        }
        else {
            $returnval = 0; // "Incorrect username/password"
        }    
    }
    else {
        $returnval = -1; // "Please enter username/password"
    }
	
    return $returnval;    
}

function get_groups($user) {
    global $host, $port, $protocol, $base_dn, $domain;

    // Use admin user in LDAP to query
    $username = "admin_username";
    $password = "password";
    
	// Active Directory server
	$connection_string = "$protocol://$host:$port";
 
	// Active Directory DN, base path for our querying user
	$ldap_dn = $base_dn;
 
	// Active Directory user for querying
	$query_user = $username."$domain";
	$password = $password;
 
	// Connect to AD
	$ldap = ldap_connect($connection_string) or die("Could not connect to LDAP");
	ldap_bind($ldap,$query_user,$password) or die("Could not bind to LDAP");
 
	// Search AD
	$results = ldap_search($ldap,$ldap_dn,"(samaccountname=$user)",array("memberof","primarygroupid"));
	$entries = ldap_get_entries($ldap, $results);
	
	// No information found, bad user
	if($entries['count'] == 0) return false;
	
	// Get groups and primary group token
	$output = $entries[0]['memberof'];
	$token = $entries[0]['primarygroupid'][0];
	
	// Remove extraneous first entry i.e. the count of the groups the user belongs to
	array_shift($output);
	
	// We need to look up the primary group, get list of all groups
	$results2 = ldap_search($ldap,$ldap_dn,"(objectcategory=group)",array("distinguishedname","primarygrouptoken"));
	$entries2 = ldap_get_entries($ldap, $results2);
	
	// Remove extraneous first entry
	array_shift($entries2);
	
	// Loop through and find group with a matching primary group token
	foreach($entries2 as $e) {
		if($e['primarygrouptoken'][0] == $token) {
			// Primary group found, add it to output array
			$output[] = $e['distinguishedname'][0];
			// Break loop
			break;
		}
	}
 
	return $output;
}

If you’re running a script on the server to test this, may not be recommended to store your username and password in the script to test this. You could alternatively run the test by doing an anonymous bind.

        $ldapbind = @ldap_bind($conn);
        if ($ldapbind) {
           echo "LDAP bind anonymous successful...";
        } else {
            echo "LDAP bind anonymous failed...";
        }

If for any reason you are unable to bind, here are a few things you could do to debug further.
1. Set debug level to 7 with the ldap_set_option. Please note that the connection parameter is not required in this function.

ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 7);

2. Make sure the following libraries are installed on the system: php-ldap, libldap-common, libaprutil1-ldap, libldap-2.4-2

3. If you want to do SSL or TLS, you should know that the default behavior is for ldap clients to verify certificates, and give misleading bind errors if they can’t validate them. This means:

  • if you’re using self-signed certificates, add TLS_REQCERT allow to /etc/ldap/ldap.conf on your clients, which means allow certificates the clients can’t validate. TLS_REQCERT can take the following values:

    never – The client will not request or check any server certificate.
    allow – The server certificate is requested. If no certificate is provided, the session proceeds normally. If a bad certificate is provided, it will be ignored and the session proceeds normally.
    try – The server certificate is requested. If no certificate is provided, the session proceeds normally. If a bad certificate is provided, the session is immediately terminated.
    demand | hard – This is the default. The server certificate is requested and a valid certificate must be provided, otherwise the session is immediately terminated.

  • if you’re using CA-signed certificates, and want to verify them, add your CA PEM certificate to a directory of your choice (e.g. /etc/ldap/certs, or /etc/pki/tls/certs, for instance), and point to it using TLA_CACERT in /etc/ldap/ldap.conf, and tla_cacertfile in /etc/ldap.conf.

Adding my first validation rule in Salesforce

It’s exciting to do Salesforce development and administration. Had to add a validation or security for a field so it’s only editable by a certain set of people or user profiles. I added a Validation Rule under Customize > Leads > Validation Rules > New.

Please note that the validation rules work on the entire object by default unless specific fields are defined within the ruleset. The following validation would enable the modification by the given set of users AND the System Administrator AND only if the Sales Channel field has been changed. The use case is to make the Sales Channel field editable only to the given set of users. So the Validation Rule will only come into picture if Sales Channel has been modified (ISCHANGED) and in which case if the user isn’t in the list, saving the object would result in a Warning/Error. If you accidentally omit the ISCHANGED part, it would imply that the Lead object itself is only modifiable by the given set of users or user groups.

NOT ($User.Id == "0053300000467Gs" || $User.Id == "005f3000004pNZ7" || $User.Id == "00540000001Ej68" ||
$Profile.Name == "System Administrator" || $Profile.Name == "Accellion Order Management") && ISCHANGED(Sales_Channel__c)

A slight tweak to the above use case. Make the field editable to only a few users or groups AND only if the prior value in the field is “From Partner”.

ISPICKVAL(PRIORVALUE(Sales_Channel__c), "From Partner") && NOT ($User.Id == "0053300000467Gs" || $User.Id == "005f3000004pNZ7" || $User.Id == "00540000001Ej68" ||
$Profile.Name == "System Administrator" || $Profile.Name == "Accellion Order Management") && ISCHANGED(Sales_Channel__c)

Personally, I prefer to use the logical && and || symbols rather than the AND/OR functions in Salesforce because it’s easily readable. Here’s a validation rule that will raise an error if:
1. Opportunity Amount when selecting Customer Type = ‘New Customers’ with Amount less than $5000 not allowed; OR
2. Opportunity Amount when selecting Customer Type = ‘Existing Customer – Replacement’ with Amount less $4000 not allowed;

(Amount < 5000 && ISPICKVAL(Type, 'New Customer')) || (Amount < 4000 && ISPICKVAL(Type, 'Existing Customer - Replacement'))

Salesforce – Implementing Triggers and Callouts

Here’s a simple use case that I implemented within Salesforce.

On saving a contact:
1. Set a field to be always true for the Contact
2. Call an external API

Create a new Trigger
To begin with, Setup > Customize > Contacts > Triggers. Create a New trigger and name it say AddPartnerToKiteworks. I’ve chosen after insert and after update as trigger points because I want the target functionality for newly created contacts as well as contacts that are modified.

trigger AddPartnerToKiteworks on Contact (before insert, before update) {
    System.debug(LoggingLevel.Info,'Executing trigger');
    for(Contact c : Trigger.new) {
        // Set the value of DoNotCall (just as an example) to always be true
        c.DoNotCall = true;
        // KiteworksConnector is a Callout class that is defined in the next section with a static method makeCallout();
        KiteworksConnector.makeCallout();
    }
}

Create the callout class
Navigate to Setup > Build > Develop > Apex Classes. Create a New trigger and name it to be same as the class definition so it’s easy to remember. On calling the trigger, I am executing a hard-coded endpoint just to test this out but you are free to call your own API endpoint or web service.

public class KiteworksConnector {
    @future(callout=true)
    public static void makeCallout() {
        HttpRequest request = new HttpRequest();
        // Set the endpoint URL.
        String endpoint = 'https://jsonplaceholder.typicode.com/posts/1';
        request.setEndPoint(endpoint);
        // Set the HTTP verb to GET.
        request.setMethod('GET');
        // Send the HTTP request and get the response.
        HttpResponse response = new HTTP().send(request);
        if (response.getStatusCode() == 200) {
            System.debug(LoggingLevel.Info,'Successful call');
            System.debug(LoggingLevel.Info,response.getBody());
        }
    }
}

Add a secure endpoint
Navigate to Setup > Administer > Security Controls > Remote Site Settings. Create a New Remote Site and name it to say jsonplaceholder_test_api and add the URL for the site. In this case it is https://jsonplaceholder.typicode.com. Check the Active flag and Save the site information.

Checking debug logs
Check out the following blog post to understand how to enable debug logs.

Some useful links:

  • APEX Triggers Introduction
  • APEX Integration REST Callouts