PDF Services too expensive? — 11/06/2017

PDF Services too expensive?

Recently, the license for one of the apps we use to generate documents in PDF format expired. And as expected, the service provider raised the subscription price for renewal, so I decided to ‘get rid of’ them and build our own custom solution. (Pretty standard as an ops engineer hey?)

The main purpose of us having this app is to ship an invoice to our partners when certain actions happen (workflow). Our partners will receive emails with a pdf file attached.

I thought it would be super easy to set up a visualforce page and simply set the following to render the page as a PDF file:

<apex:page renderAs="pdf"></apex:page>

However, I was wrong, all stylings in the visualforce page are dismissed when salesforce converts it to PDF, the resulting file looks like an image taken in 1880s…

Therefore I had to dig further to see if there is another way of solving it. After few minutes of reading, I realised javascript button can be used to trigger a page PRINT action, this might be the alternative I am after.

After few attempts, I got the solution to work. The final product is simply a visualforce page running on a salesforce sites (so users, our partners, don’t need licenses to access files), and on the bottom of the page, I place a print button like so.

<input type="button" value="Print" onclick="window.print()" />

Here is the flow:

  1. Whatever actions trigger emails to partners;
  2. Within the email, there is a link similar to xxx.force.com?id= that opens the ‘record’ on a salesforce site with a desired layout;
  3. Users open the link and review the page and print or save to local storage if needed.

This challenge shows how we can leverage salesforce sites and other technologies outside of salesfore to achieve business needs with a minimum cost.

Salesforce API – Creating Objects — 22/04/2017

Salesforce API – Creating Objects

Last year, we talked about how to get an access token to make api request to Salesforce.

Now, we are going to use this token and create a something with it. Again, the method is written in Python. You may, of course, write it in a different language, but I found python to be a very handy programming language when I try to get things up and running quickly.

def createRecord(instance, sessionId, recordApiName, fields):
    url = 'https://'+instance+'.salesforce.com/services/data/v37.0/sobjects/'+recordApiName+'/'
    headers = {
        'Content-Type': "application/json",
        'Sforce-Auto-Assign': "FALSE",
        'Authorization': "Bearer " + sessionId
    }
    payload = json.dumps(fields, ensure_ascii=False)
    response = requests.request("POST", url, data=payload, headers=headers)
    return response.text

Notice the method takes four inputs,
– instance is simply the org you are using, ‘cs6’ for example;
– sessionId is what we got from the previous article;
– recordApiName is basically the api name for the object you are updating, e.g. Account
– fields, this is a JSON formatted string that contains the key value pairs of the data you want to insert/update in Salesforce.

There you have it, a simple and useful way to manipulate data in your Salesforce org.

Happy Lunar New Year — 30/01/2017
Salesforce API – Getting Access Token — 15/01/2017

Salesforce API – Getting Access Token

In the article before, I described how to set up Heroku for integration. In this post, I will start with the basic — obtaining the access token.

Just a bit of the background. I recently run into a problem where customers are emailing enquiries to whichever company email address they found in their inboxes. Meaning a technical support agent might be getting an enquiry about a product feature, and the agent will have manually assign the case back to the correct queue.

It felt like a very common problem, however, I couldn’t find any solutions online whatsoever. Therefore, I (again) decided to design a solution for it myself. Due to the system limitations of apex programming, I don’t think it’s even possible to build a classifier inside the Salesforce platform. I had to build it externally and push the result back to Salesforce.

So the complete flow is:

  1. Salesforce creats cases from emails
  2. Salesforce sends case descriptions along with the Case IDs to
  3. The classified cases based on historical data and send results back to Salesforce

Here, we are actually only going to talk about the last step — send data back to Salesforce.

The external application is written in Python, a very powerful programming language. The reason why I chose python is not only because it’s fast to build a web application on Django, but also because python comes with heaps of free libraries we can use e.g. sklearn.

There are many ways to achieve this, I am going to introduce the soap api, just because it only requires your Salesforce user login email and password.

Here is the method in Python:

def login(instance, userName, password):
    url = "https://" + instance + ".salesforce.com/services/Soap/u/37.0"
    payload = u"""<?xml version="1.0" encoding="utf-8" ?>
    <env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
    <env:Body>
    <n1:login xmlns:n1="urn:partner.soap.sforce.com">
    <n1:username>""" + userName + """</n1:username>
    <n1:password>""" + password + """</n1:password>
    </n1:login>
    </env:Body>
    </env:Envelope>"""
    headers = {
        'content-type': "text/xml; charset=UTF-8",
        'soapaction': "login"
    }
    response = requests.request("POST", url, data=payload, headers=headers)
    loginXmlRoot = ET.fromstring(response.text)
    sessionId = loginXmlRoot[0][0][0][4].text
    return sessionId

When you call this method, it calls Salesforce API and obtains the session id (the token), which can be later used to ‘do some damages’.

In the next blog, I will post how to use this token to create objects in Salesforce via api.

Tracking the actual time spent on a record — 09/01/2017

Tracking the actual time spent on a record

There is no transparency to identify the total time taken for an agent to work on a particular case and which type of inbound enquiries has the highest rate of neglect from a single agent.

Simply subtracting the case open date from the case closed date won’t provide us with the information we are after because this is limited to the lifetime of a case from opened to closed. In addition, it will not indicate the number of users who opened the case.

I contacted Salesforce support to see if I whether there was a component that could achieve what I was after before investing time into building this, however to no avail.

The build

There are two requirements to this:

  • A custom object ‘time log’ to capture the duration
  • A Visualforce component that logs the start and end time each time a record is viewed

We firstly create a custom object with lookup fields to the record you wish to track the duration for, in this instance we will use the Case standard object. Next we create a number field in the new custom object to store the duration.

We will use JavaScript in conjunction with Visualforce to log the duration. Below is what the Visualforce page looks like.

<apex:page standardController="Case">

<script src="/soap/ajax/37.0/connection.js"></script>
<script src="/soap/ajax/37.0/apex.js"></script> 

<script type="text/javascript">
var timerStart = Date.now();

window.onbeforeunload = function(){
    var timeObj = new sforce.SObject("Time_Log__c");
    timeObj.case__c = "{!Case.id}";
    timeObj.Sec__c = (Date.now()-timerStart)/1000;
    var tmp = [timeObj];
    sforce.connection.create(tmp);
}

</script>
</apex:page>

As simple as that! In the Visualforce page, we define a start time when a user opens the page, and create the custom object Time Log when the window/tab is closed by the user.

However, this is only compatible with standard page, it would not work properly in the service console, because we are technically not closing a ‘window’, but a sub-tab within a window.

In the next article, I will explain how it can be solved by using the integration tool provided by Salesforce and how we can report on agent performance with this object.

Setting Up Heroku – Django — 03/01/2017

Setting Up Heroku – Django

I think programming in another language is pretty important when it comes down to solving real world challenges, the reason is that Salesforce has a lot of system limitations and the fact that Apex isn’t flexible enough to handle all sorts of problems.

Today, we are going to set up a Django app in Heroku and make the first deployment.

Firstly, you will need to be familiar with python and how the Django framework works. And I strongly recommend you to use virtual environment to avoid making changes to system files.

In order to use Heroku, you are gong to need a free Heroku account, Heroku is owned by Salesforce by the way. Once you have it, you can download the Heroku CLI from their website, the Mac Version can be found here.

Next, we will need to create an app on Heroku, you can do this via Heroku website or by running “heroku create” on your local project folder (your git repository).

“””When you create an app, a git remote (called heroku) is also created and associated with your local git repository.”””

By running “git push heroku master“, you can easily push changes to Heroku. Salesforce/Heroku handles the deployment process so easily, you won’t even need to worry about things such as dependencies, as long as you declare everything in your files (for Django web applications).

I think this should be enough for you to get the app up and running provided you already know how to run the website on your local machine.

In the next article, I will talk more about how to integrate your python app with your Salesforce instance.

More information on how to set up a Heroku app can be found at https://devcenter.heroku.com/articles/getting-started-with-python#introduction, and happy new year.

Happy New Year — 01/01/2017
Bypassing the dynamic dashboard limit — 29/12/2016

Bypassing the dynamic dashboard limit

Salesforce provides an out-of-the-box solution in the form of a dynamic dashboard which highlights a user’s information. This out-of-the-box solution is all fine and good until you hit the limit.

I’ve come across a solution which requires a little code and Google Charts. Google charts is a free to use JavaScript visualization tool which synchronizes perfectly with Visualforce.

Problem: Org has reached the limit for number of dynamic dashboards 😦

Today we will draw a pie chart which shows closed cases by a user grouped by case type(feedback, compliment and complaint). Firstly, we will start with the controller – this is where we query the data which dynamically renders the chart every time the page loads.

Our custom controller will include a constructor to initialize the data when the chart loads. We then set the variables which will be called later in our Visualforce page – I have listed three for now and assigned it to zero. The data is then queried and a counter is performed for every case that contains ‘X’ type.

public class MyClosedCaseController {
    public integer CustomerFeedback{
        get ; set;
    }
    public integer CustomerComplaint{
        get ; set;
    }
    public integer CustomerCompliment{
        get ; set;
    }
    public MyClosedCaseController (){
        system.debug('userId: ' + UserInfo.getUserId());
        CustomerFeedback = 0;
        CustomerComplaint = 0;
        CustomerCompliment = 0;
        for(Case c: [
            SELECT Type FROM Case WHERE Status = 'Closed' AND ownerid =: UserInfo.getUserId()
            AND ClosedDate = TODAY
        ])
        {
            If (c.Type == 'Customer Feedback') CustomerFeedback ++;
            If (c.Type == 'Customer Complaint') CustomerComplaint ++;
            If (c.Type == 'Customer Compliment') CustomerCompliment ++;
        }
    }
}

Our controller is complete! Now to draw our chart with Google Charts. Google provides an in-depth tutorial on how to use the charts – I found this easy to understand after drawing my second chart.

First we set the standard Visualforce tags, I’ve added html as it will help us style later when we add more charts to the page. We also add a markup for the custom controller we created earlier – this will allow us to call the variables (CustomerCompliment, CustomerComplaint and CustomerFeedback).

I’ve added comments in the source code below which summarizes the purpose of each action. The important part is to create a div tag in the body and add the markup (variable – piechart) which holds the chart that was created in the earlier step.

<apex:page controller="MyClosedCaseController" sidebar="false" showHeader="false">
    <html>
        <head>
            <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
            <script type="text/javascript">
              // loads the chart packages
              google.charts.load('current', {'packages':['corechart']});
              // set a callback to run when the Google Visualization API is loaded
              google.charts.setOnLoadCallback(drawChart);

              // drawing the chart
              function drawChart() {

                // constructing the data table
                var data = new google.visualization.DataTable();
                data.addColumn('string', 'Case Type');
                data.addColumn('number', 'Quantity');
                data.addRows([
                    ['Compliment', {!CustomerCompliment}],
                    ['Complaint', {!CustomerComplaint}],
                    ['Feedback', {!CustomerFeedback}]
                ]);

                // customizing the chart
                var options = {
                    'title': 'My Closed Cases By Type',
                    'width': 800,
                    'height': 600,
                };

                var chart = new google.visualization.PieChart(document.getElementById('piechart'));

                chart.draw(data, options);
              }
            </script>
        </head>
        <body>
<a href="ap4.salesforce.com/SalesforceReportId>
<div id="piechart" />
</a>

</body>
    </html>
</apex:page>

The chart should end up looking something like this:

chart

I hope this helps you as much as it did help me. Please feel free to ask questions.

Resend Approval Email — 18/12/2016

Resend Approval Email

Hello! Welcome to another interesting ‘world-class’ problem – resending approval emails that accept approvers’ replies. (I was, again, surprised that Salesforce doesn’t support this functionality natively.)

Start with the problem:

  • Approvers sometimes forget to reply to approval emails nor be bothered to login into salesforce to approve the record.
  • Once an item is submitted for approval, there is no way to remind the approver unless the raiser recalls the submission and resubmits.

This is causing a lot of headaches because manual emails and human interactions will be involved, and mistakes happen when there are back and forth communications.

Here is the real challenge we have, the executive team never want to have another login, we therefore need to make their life as easy as possible, and simply set up a workflow email alert won’t help much because it wouldn’t “read” the reply from the approvers.

In fact we just want to replicate the “Submit for Approval” button but only send the reminder email to the latest pending approver.

Solution design, to achieve this, we need:

  • an Email Template similar to the initial submission email that contains the record ID (which is later used to find the record)
  • an Apex class that implements Messaging.InboundEmailHandler to read the inbound email content
  • a Salesforce Email service to send and receive emails
  • a custom button on record detail page to send the email from the email service we set up (using URL Hack I wrote before)

Let’s get started!

Both email template and the button design is easy, I will just skip them, just don’t forget to include the detail link to the related record e.g. [instance].salesforce.com/[your-record-id], we are going to use this URL to allocate the record in salesforce.

Now in step two, we need to write some codes.

The Apex class will have to implement Messaging.InboundEmailHandler which contains a global method called handleInboundEmail that takes two inputs and returns Messaging.InboundEmailResult.

global class ObjectInboundEmailHandler implements Messaging.InboundEmailHandler {
    global Messaging.InboundEmailResult handleInboundEmail(Messaging.InboundEmail email, Messaging.InboundEnvelope envelope) {
    }
}

We are going to mainly focus on Messaging.InboundEmail email and try to dig some information from it.

Before that, let me introduce regex for those who aren’t familiar with it, here is the link to Wikipedia (please donate some spare changes to support them 🙂 ), basically we are going to use regex to extract the record ID.

Let’s assume your object ID always starts with “abc”, then you can use the following code to find record id inside the email body (assuming you did not forget to include the full URL of the record in the email body as mentioned above, which makes sense).

Pattern p = Pattern.compile('abc.{12}');
Matcher pm = p.matcher(email.plainTextBody.trim());
String ObjectId = null;
if (pm.find()){ // Object id found
    System.debug(pm.group(0));
    ObjectId = pm.group(0);
}
System.debug('ObjectId ' + ObjectId);

It’s not difficult to read the response from the first line and second line of the email (we do this because we want to replicate the standard button, however, feel free to add more logics on top of that).

Once we have the record ID and the response, we can then apply it to the following SOQL query to find the latest approval step for the record and action it on behalf of the email sender (the approver) based on user response. I’ve also added a simple validation to make sure the email sender is a valid salesforce user.

String res = email.plainTextBody.trim().split('\n')[0];
if (res != null){
    String action = null;
    List<ProcessInstance> pis = [
        SELECT Id, (SELECT Id, ActorId, ProcessInstanceId FROM Workitems)
        FROM ProcessInstance
        where TargetObjectId =: ObjectId
    ];
    // check sender email to see if sender is a salesforce user
    System.debug('email.fromAddress ' + email.fromAddress);
    List<User> users = [select id from user where email =: email.fromAddress];
    User user = users != null && !users.isEmpty() ? users[0] : null;
    System.debug('user ' + user);
    if (user == null) {
        /* your own error handling */
        return null;
    }
    // attach the email to Object
    insert new Task(
        ownerid = user.id,
        Description =  email.plainTextBody,
        Priority = 'Normal',
        Status = 'Complete',
        subject = email.subject,

        whatId = ObjectId
    );

    // now standardise the action
    if (
        res.trim().toLowerCase() == 'yes' ||
        res.trim().toLowerCase() == 'approve' ||
        res.trim().toLowerCase() == 'approved'
    ) {
        action = 'Approve';
    }
    else if (
        res.trim().toLowerCase() == 'no' ||
        res.trim().toLowerCase() == 'reject' ||
        res.trim().toLowerCase() == 'rejected'
    ) {
        action = 'Reject';
    }
    if (action != null){
        for (ProcessInstance pi : pis){
            if (pi.Workitems != null && !pi.Workitems.isEmpty()){
                ID tmp = pi.Workitems[0].id;
                Approval.ProcessWorkitemRequest req = new Approval.ProcessWorkitemRequest();
                req.setComments(comment);
                req.setAction(action);
                req.setWorkitemId(tmp);
                Approval.ProcessResult ProcessResult = Approval.process(req);
            }
        }
    }
    else{
        /* your own error handling */
        return null;
    }
}

(Note that the actual approver is always going to be you — the inbound email handler runner, that’s why I saved an email object as a reference, you may also want to append the actual approver’s name in the comment section.)

Now step three, the email service. It’s actually very simple:

  1. Go to setup page (our favourite as a Salesforce Admin/Dev)
  2. In the search box type in “email services” and click on it
  3. Click on “New Email Service”
  4. Give it a name and put “ObjectInboundEmailHandler” (or your custom class name) under Apex Class
  5. Put your email under Route Error Emails to This Email Address (optional)

You will also need to verify this newly created email address as an Organization-Wide Email Address in order to send reminder email from it.

And …

There you have it!

The final product is a detail page button that sends a reminder email from a custom email service that has the ability to read approvers’ responses and approve/reject the current step for you, automatically.

screen-shot-2016-12-18-at-12-19-01-pm

CSAT in Salesforce?? — 11/12/2016

CSAT in Salesforce??

Salesforce does not provide any ways to capture customers’ feedbacks as a standard feature, most companies will have to rely on pricey appexchange apps to achieve this.

Considering what we need is a simple way to rate resolved cases, buying something that cost $x per user per month just sounds incredibly expensive.

Fortunately, there is still hopes. Salesforce gives us the ability to create something called Salesforce Site, we can use this to gather end users feedbacks via email.

This is how it’s done:

  1. Salesforce automatically/manually sends an email to the customer
  2. The customer clicks on a link in the email (maybe behind a happy/sad face)
  3. A customer satisfaction feedback can be created in Salesforce and linked to the case.

Sounds extremely useful hey? Let’s get started. (I won’t be posting any code for this tutorial btw)

Firstly, we need to create a custom object to hold the feedback value. This should be easy for all of us, just make sure there are at least three fields in the custom object:

  1. Case__c
  2. Score__c
  3. Comment__c

These names are pretty self-explanatory, I won’t dig too much into it.

Then, we need a controller to handle user selections. Since it’s a link inside an email template, they can only be GET requests. We use the standard method in Apex to read the request that looks like this .salesforce.com/?score=x&caseid=

ApexPages.currentPage().getParameters().get('score');

And then, the Apex class will parse this request and create a feedback with score = x and attach it to the case with .

The next step is to create a visualforce page that is used for customers to fill in feedback comments. This should be very easy, again, I am using the Google Material Design to beautify the page. (and don’t forget the submit button)

Almost there, we now need to create a Salesforce site, we do this by going into Setup -> Develop-> Sites. (If you haven’t created one before, be sure to get a domain name for your site. ) Click on New to create a new site. Put on the label and a preferred URL, and put the name of the visualforce page we just created under “Active Site Home Page”. Save it, and test it make sure it’s working by clicking on the link under “Custom URLs”.

This should open an external page that contains a dropdown to pick a score and a free text area to type in comments. Of cause this page won’t work, simply append the URL with any random Salesforce case id, you will see a feedback has been created under that case and the “created by user” is called something site user.

Lastly, we need an email template, to keep it straightforward and to see the result immediately, we quickly create a “Custom Email” template that contains the following three links in the email footer.

<a href="<domain>.salesforce.com/yrpage?caseid=<case id>&score=1>Happy</a>
<a href="<domain>.salesforce.com/yrpage?caseid=<case id>&score=0>I'm just fine</a>
<a href="<domain>.salesforce.com/yrpage?caseid=<case id>&score=-1>Not Happy</a>

Notice the case id is dynamic and the score is static, so customers won’t create feedbacks for the same case.

To finish up the article, I suggest you add few more validation rules around feedback creation, for example, only one feedback can be created within 24 hour period for the same case. Or you may want to use a Smiley face in your email templates instead of having just some links.

Later on, we will be able to simply run a feedback with case report and see which agent (case closed by) got the highest CSAT rating 🙂