Salesforce Slack Integration

Introduction

In this blog, I am going to explain how to set up chatbot with salesforce and Slack. Nowadays Chatbots are gaining more and more popularity thorough conducts a conversation via auditory or textual methods.we will build chat bot for Slack using Botkit — a popular and open source bot development kit written in node.js jsforce for Salesforce integration with node.js. 

Create a bot in Slack

Login you Slack account and go to bot configurations page, enter “any name you want ” in the username field and click Add Bot Integration.

you will be brought to an edit configuration page. Under Integration Settings section, there is an API Token which we will use it for later in the code for authentication.

Setup configuration

we are built on node js you can do it by npm init and npm install packages. but here is the package.JSON file which you can use.

{
  "name": "slacksalesforce",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "botkit": "^0.6.3",
    "express": "^4.16.1",
    "http": "0.0.0",
    "jsforce": "^1.8.0"
  }
}

Run the following command

npm install

Create a new file index.js in root directory and add the following code

var Botkit = require('botkit');
var jsforce = require('jsforce');
var conn = new jsforce.Connection({
    loginUrl: 'https://login.salesforce.com'
});
var username = '<Your Salesforce ID>';
var password = 'Password +Token';
conn.login(username, password, function (err, userInfo) {
    if (err) { return console.error(err); }
});
var controller = Botkit.slackbot();
var bot = controller.spawn({
    token: "xoxb-<Slack Token>"
})
bot.startRTM(function (err, bot, payload) {
    if (err) {
        throw new Error('Could not connect to Slack');
    }
});
controller.hears(['help', 'Salesforce '], 'interactive_message_callback,direct_message,direct_mention,mention', function (bot, message) {
    bot.startConversation(message, function (err, convo) {
        if (!err) {
            convo.say('Ok . Let me Help you ! ');
            convo.ask('Which Object data , say Contact or Opportunity or Account?', function (response, convo) {
                if (response.text == 'Contact') {
                    convo.say('Ok .You are looking for  ' + response.text + ' data ');
                    convo.ask('Can i have contact Name ?', function (response, convo) {
                        conn.query("SELECT Id,Title, Name,email ,Phone FROM Contact where name=\'"+response.text+"\' Limit 1", function (err, result) {
                            console.log(result);
                            var name = result.records[0].Name ; 
                            var email = result.records[0].EMail ; 
                            var phone= result.records[0].Phone; 
                            var id = result.records[0].Id ; 
                            console.log(name);
                            bot.reply(message, {
                                attachments: [
                                    {
                                        "fallback": "Required plain-text summary of the attachment.",
                                        "color": "#36a64f",
                                        "pretext": "Contact Details are here",
                                        "author_name": name,
                                        "title": "Contact Details",
                                        "title_link": "https://fscttt-dev-ed.my.salesforce.com/"+id,
                                        "text": "Details are here ",
                                        "fields": [
                                            {
                                                "title": "Name",
                                                "value": name,
                                                "short": false
                                            },
                                            {
                                                "title": "Phone",
                                                "value": phone,
                                                "short": false
                                            },
                                            {
                                                "title": "Id",
                                                "value": id,
                                                "short": false
                                            }
                                            

                                        ],
                                        "footer": "Slack API",
                                        "footer_icon": "https://platform.slack-edge.com/img/default_application_icon.png",
                                        "ts": 123456789
                                    }
                                ]
                            });


                            convo.say("Ok! Goodbye.");
                            convo.next();

                        });
                    });
                }
                if (response.text == 'Account') {
                    convo.say('Ok .You are looking for  ' + response.text + ' data ');
                    convo.ask('Can i have Account Name ?', function (response, convo) {
                    });
                }
                if (response.text == 'Opportunity') {
                    convo.say('Ok .You are looking for  ' + response.text + ' data ');
                    convo.ask('Can i have Opportunity  Name ?', function (response, convo) {
                    });
                }
                convo.next();
            }); // store the results in a field called nickname
            convo.on('end', function (convo) {
                if (convo.status == 'completed') {
                    bot.reply(message, 'OK! I Hope you find the infomration');
                } else {
                    bot.reply(message, 'OK, nevermind!');
                }
            });
        }
    });
});
controller.hears('^stop', 'direct_message', function (bot, message) {
    bot.reply(message, 'Goodbye');
    bot.rtm.close();
});

Now go to your slack channel and type help to start interacting with the bot  as shown below. As of now, the code is working to fetch contact details to slack but you can extend to other levels.

Understand the Botkit

Before we make it be a real chatbot, let’s dive into the code and see how Botkit works.  Open the index.js file and you will see a lot of controller.hears() functions. In fact, hears() is a function provided by Botkit to listen messaging events based on matching keywords in message text and those are help and Salesforce

Here’s a simple example of hears() function;

controller.hears(['help', 'Salesforce '], 'interactive_message_callback,direct_message,direct_mention,mention', function (bot, message) {
    bot.startConversation(message, function (err, convo) {
}
})

I’ve added indirect_mention,direct_message,interactive_message_callback this parameter so this function can be fired when I say “@salesforce “ in the channel
Here’s the full list of parameter options from Botkit github:

Event Description
message_received This event is fired for any message of any kind that is received and can be used as a catch-all
ambient Ambient messages are messages that the bot can hear in a channel, but that does not mention the bot in any way
direct_mention Direct mentions are messages that begin with the bot’s name, as in “@bot hello”
mention Mentions are messages that contain the bot’s name, but not at the beginning, as in “hello @bot”
direct_message Direct messages are sent via private 1:1 direct message channels

The third parameter is a callback receiving bot and message 
the bot can be used to reply the received message using the functionreply (message, 'response text').

Apart from that, bot holds functions and data of slackbot and joined channels.

You can find the code here https://github.com/rajamohanvakati/Slack-Salesforce

Salesforce Skype Bot

Introduction

In this blog, I am going to explain the step by step how to set up a skype bot with salesforce by using node js,jsforce, and botbuilder.

Setup Node Js Project

1. Create an empty folder to start the node js project.
2. Create a new package.json file and paste the below code into the package.json file 

{
  "name": "bot",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node app.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "botbuilder": "^3.1.1",
    "jsforce": "^1.8.0",
    "restify": "^4.1.1"
  }
}

3. Run npm install from the command promote to load all the node modules from the package.json
4.Now create a new file and name it as app.js and paste this code into app.js file.

var restify = require('restify');
var builder = require('botbuilder');
var server = restify.createServer();
var jsforce = require('jsforce');

server.listen(process.env.port || process.env.PORT || 8080, function () {
console.log('%s listening to %s', server.name, server.url);
});
var connector = new builder.ChatConnector({
appId: "Microsoft App Id",
appPassword: "App password"
});
var conn = new jsforce.Connection({
loginUrl: 'https://login.salesforce.com'
});
var username = 'salesforce user name';
var password = 'password and token';
conn.login(username, password, function (err, userInfo) {
if (err) { return console.error(err); }
});
console.log(conn);

var bot = new builder.UniversalBot(connector);
server.post('/api/messages', connector.listen());

bot.on('conversationUpdate', function (message) {
// Check for group conversations
if (message.address.conversation.isGroup) {
    // Send a hello message when bot is added
    if (message.membersAdded) {
        message.membersAdded.forEach(function (identity) {
            if (identity.id === message.address.bot.id) {
                var reply = new builder.Message()
                    .address(message.address)
                    .text("Hello everyone!");
                bot.send(reply);
            }
        });
    }

    // Send a goodbye message when bot is removed
    if (message.membersRemoved) {
        message.membersRemoved.forEach(function (identity) {
            if (identity.id === message.address.bot.id) {
                var reply = new builder.Message()
                    .address(message.address)
                    .text("Goodbye");
                bot.send(reply);
            }
        });
    }
}
});

bot.on('contactRelationUpdate', function (message) {
if (message.action === 'add') {
    var name = message.user ? message.user.name : null;
    var reply = new builder.Message()
        .address(message.address)
        .text("Hello %s... Thanks for adding me. Say 'hello' to see some great demos.", name || 'there');
    bot.send(reply);
} else {
    // delete their data
}
});

bot.on('deleteUserData', function (message) {
// User asked to delete their data
});

bot.use(builder.Middleware.dialogVersion({ version: 1.0, resetCommand: /^reset/i }));
bot.endConversationAction('goodbye', 'Goodbye :)', { matches: /^goodbye/i });
bot.beginDialogAction('help', '/help', { matches: /^help/i });
bot.dialog('/', [
function (session) {
    // Send a greeting and show help.
    var card = new builder.HeroCard(session)
        .title("Salesforce Skype Bot")
        .images([
            builder.CardImage.create(session, "https://c1.sfdcstatic.com/content/dam/web/en_us/www/images/home/sfdc-jetpack-card.jpg")
        ]);
    var msg = new builder.Message(session).attachments([card]);
    session.send(msg);
    session.beginDialog('/menu');
},
function (session, results) {
    // Display menu
    session.beginDialog('/menu');
},
function (session, results) {
    // Always say goodbye
    session.send("Ok... See you later!");
}
]);

bot.dialog('/menu', [
function (session) {
    builder.Prompts.choice(session, "What would you like to do?", "posttochat|CreateOrder|ContactDetails|(quit)");
},
function (session, results) {
    if (results.response && results.response.entity != '(quit)') {
        // Launch demo dialog
        session.beginDialog('/' + results.response.entity);
    } else {
        // Exit the menu
        session.endDialog();
    }
},
function (session, results) {
    // The menu runs a loop until the user chooses to (quit).
    session.replaceDialog('/menu');
}
]).reloadAction('reloadMenu', null, { matches: /^menu|show menu/i });

bot.dialog('/posttochat', [
function (session) {
    builder.Prompts.text(session, "Please say the message to post to chatter?");
},
function (session, results) {
    // session.userData.name = results.response;
    //builder.Prompts.number(session, "Hi " + results.response + ", How many years have you been coding?");
    conn.chatter.resource('/feed-elements').create({
        body: {
            messageSegments: [{
                type: 'Text',
                text: results.response
            }]
        },
        feedElementType: 'FeedItem',
        subjectId: 'me'
    }, function (err, result1) {
        if (err) { return console.error(err); }
        console.log("Id: " + result1.id);
        console.log("URL: " + result1.url);
        console.log("Body: " + result1.body.messageSegments[0].text);
        console.log("Comments URL: " + result1.capabilities.comments.page.currentPageUrl);
        session.send("Message is posted Succefully: Record is for your reference " + result1.id);

    });

},
function (session, results) {
    // The menu runs a loop until the user chooses to (quit).
    session.replaceDialog('/menu');
}
]);

bot.dialog('/CreateOrder', [

function (session) {
    builder.Prompts.text(session, "What is the name of the order");
},
function (session, results) {
    conn.sobject("Order").create({ Name: results.Name, EffectiveDate: '2018-02-01', Status: 'Draft', Contract: '8006A0000000N5M', Account: '0016A000004BtSv' }, function (err, ret) {
        if (err || !ret.success) { return console.error(err, ret); }
        session.send("Order is created with record is  " + ret.id);
    });
},
function (session, results) {
    // The menu runs a loop until the user chooses to (quit).
    session.replaceDialog('/menu');
}

]);

bot.dialog('/ContactDetails', [
function (session, results) {
    session.send("No Logic is implemented here Simply");
    session.replaceDialog('/menu');
}

]);








 

5.Change appId & appPassword with your own appId and appPassword for chatbot and Salesforce username and password to get data from the salesforce.To get App Id and password follow below steps.

6.First login into your Microsoft account.

7.Goto this link: Click Here and complete sign in process 

8.Click on Register a bot and create a new bot.

9.Click create  button to register your bot Register an existing bot built using Bot Builder SDK

10.Now fill in your required details, select your bot image as shown below

11.Click on Create Microsoft App ID and password and then click on generate app Password to continue 

12.Now You got your App ID and Password and update them in code.

var connector = new builder.ChatConnector({
appId: "7fd91532-0cb1-49e6-8100-883682ece5da",
appPassword: "twrITAZ192>_aarsGUG69;&"
});

13.Now you need to add https messaging endpoint to listen for requests.

14.You can create one for free that will run locally on your system using the tool called ngrok and Download ngrok from here  Click Here .You will get a zip file, Extract that zip file into any folder and then open the file named ngrok.exe

15.Type and enter : ngrok http 8080

16.Now copy the https address from the command line of ngrok. Make sure you don’t close the ngrok after creating address cause then the endpoint address will not listen to any request.

17.Now paste this address into endpoint field and add api/messages” in the end as shown below on Bot Registration configuration sections.

 

18.Click on the register and save the bot.
19.Now go back to command prompt and type ‘ node app.js ‘ to start your node js application.
20.After the application starts you can test from the test section on the application.

Then type /help to start the bot conversion as shown below.

As shown in the above image you can post to chatter from the skype chatbot. once you test your application you can push the skype and use it.

Understand the Code

You can register the bot chat connect from the below code and it is going to Create chat connector for communicating with the Bot Framework Service

var connector = new builder.ChatConnector({
appId: "7fd91532-0cb1-49e6-8100-883682ece5da",
appPassword: "twrITAZ192>_aarsGUG69;&"
});

And most important think how we are going to establish the conversation as the communication between a bot and a user through one or more dialogs. A dialog, at its most basic level, is a reusable module that performs an operation or collects information from a user. the below code shows how to start the dialog

bot.dialog('/', [
function (session) {
    // Send a greeting and show help.
    var card = new builder.HeroCard(session)
        .title("Salesforce Skype Bot")
        .images([
            builder.CardImage.create(session, "https://c1.sfdcstatic.com/content/dam/web/en_us/www/images/home/sfdc-jetpack-card.jpg")
        ]);
    var msg = new builder.Message(session).attachments([card]);
    session.send(msg);
    session.beginDialog('/menu');
},
function (session, results) {
    // Display menu
    session.beginDialog('/menu');
},
function (session, results) {
    // Always say goodbye
    session.send("Ok... See you later!");
}
]);

You can refer more details on dialogs  below links

https://docs.microsoft.com/en-us/bot-framework/nodejs/bot-builder-nodejs-quickstart

https://docs.microsoft.com/en-us/bot-framework/nodejs/bot-builder-nodejs-dialog-overview

 

 

Control Flow Behavior with Lightning Component

Introduction 

In this blog post is the extension for this two posts Salesforce Flow Actions and Invoking Flow From Lightning Component. In this post, I am going to explain how to set up the flow with input and output variable and finish behavior from the Lightning component.This is more or less similar to control the flow behavior from the visualforce. I am going to reuse the same flow which I created for the  Salesforce Flow ActionsBut create a different input and output variable to store the values instead of using screen input variable to store the value.

Go to developer console  and create a new Lightning component with below code

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId" access="global" >
    
    <aura:handler name="init" value="{!this}" action="{!c.init}"/>
    <lightning:flow aura:id="flowId" onstatuschange="{!c.handleStatusChange}" />
       
</aura:component>

Here is the basic code for the controller.

({
    init : function (component) {
        var flow = component.find("flowId");
        flow.startFlow("SOinser");
    },
    handleStatusChange : function (component, event) {
        
    },
})

Set Flow Variables

 When you embed a flow in a Lightning component, give the flow more context by initializing its variables, sObject variables, collection variables, or sObject collection variables. In the component’s controller, create a list of maps, then pass that list to the startFlow method on the component. Now, let’s start modifying the code to pass the default values when the flow is launched from the component as shown below.
({
    init : function (component) {
        var flow = component.find("flowId");
        var inputVariables = [
            { name : "salesorderid", type : "String", value: "1234" }, 
            { name : "poid", type : "String", value:"PO#123" },
            { name : "orderamountid", type : "Currency", value:9000 },
            { name : "disid", type : "Number", value:12 },
            { name : "shipingid", type : "Currency", value: 120 }
        ];
        flow.startFlow("SOinser" , inputVariables);
    },
    handleStatusChange : function (component, event) {
        
    },
})

In the above controller code, we have used inputVariables array to hold the default values and passed it to the startFlow method as the second argument. You can see the flow has populated the values as shown below.

Even if you would like to populate a value form the apex class you will be able to do it as shown below.Create a server-side apex controller as shown below  and refer from the component

public class TestFlow {
    @AuraEnabled
    public static Account getAccountId(){
        
        return [Select Id , Name from Account Limit 1];
    }
    
}

In the Component controller, you can modify the code as shown below. It’s going to fetch server-side data on the initialization of the component and populate it the flow as an input variable.

 var action = component.get("c.getAccountId");
        action.setCallback(this, function(response) {
        	 var inputVariables = [
               {
                  name : "account",
                  type : "SObject",
                  value: response.getReturnValue()
          	     }
                 
            ];
                    flow.startFlow("myFlow", inputVariables);

            
        });

Below table shows some of the data types and its valid values format.

Flow Variable Type Type Valid Values
Text String { name : “textVar”, type : “String”, value: “Passing String” }
Number Number          { name : “numVar”, type : “Number”, value: 30 },
Currency Currency          { name : “cncyVar”, type : ” Currency “, value: 30.12 },
Boolean Boolean          { name : “boolVar”, type : ” Boolean “, value: true },
Date Date          { name : “dateColl”, type : “String”, value: [ “2016-10-27”, “2017-08-01” ] },
Date/Time DateTime          { name : “dateTimeColl”, type : “String”, value: [ “2016-10-27”, “2017-08-01:00:00:00:T” ] },
sObject SObject 1 .{ name : “account”, type : “SObject”, value: {“Id” : component.get(“v.accountId”),”Rating” : “Warm”}}

2. { name : “account”, type : “SObject”, value:                  value: response.getReturnValue()}}

Get Flow Variable

Even you can able to get the Flow variable values can be displayed or referenced in a Lightning component . Once you’ve embedded your flow in a custom Lightning component, use the onstatuschange action to get values from the flow’s output variables. Output variables are returned as an array. Now here is the mockup code that will display the result to component from the flow variables

 
    <aura:attribute name="salesorderid" type="String" />
    <aura:attribute name="poid" type="Decimal" />
    <aura:attribute name="orderamountid" type="Currency" />
    <aura:attribute name="disid" type="Number" />
    <aura:attribute name="shipingid" type="Currency" />
   
    <p><lightning:formattedText value="{!v.salesorderid}" /></p>
    <p><lightning:formattedText  value="{!v.poid}" /></p>
    <p><lightning:formattedText value="{!v.orderamountid}" /></p>
    <p><lightning:formattedText  value="{!v.shipingid}" /></p>

Now from the controller handleStatusChange  method you can able get the flow status by calling     event.getParam(“status”)  and   event.getParam(“outputVariables”)  return the output variables   as shown below .

 handleStatusChange : function (component, event) {
        if(event.getParam("status") === "FINISHED") {
            console.log('Runing');
            var outputVariables = event.getParam("outputVariables");
            console.log(outputVariables) ;
            var outputVar;
            for(var i = 0; i < outputVariables.length; i++) {
                outputVar = outputVariables[i];
                if(outputVar.name === "salesorderid") {
                    component.set("v.salesorderid", outputVar.value);
                }  
            }
        }
        
    }

Here is display result value console for the same.

Set Finish Behavior

By default, when a flow user clicks Finish, the component starts a new interview and the user sees the first screen of the flow again. However, you can shape what happens when the flow finishes by using the onstatuschange action. To redirect to another page, use one of the force:navigateTo* events such as force:navigateToObjectHome or force:navigateToUrl as shown below on handleStatusChange controller method.

 if(outputVar.name === "redirect") {
                    var urlEvent = $A.get("e.force:navigateToSObject");
                    urlEvent.setParams({
                        "recordId": outputVar.value,
                        "isredirect": "true"
                    });
                    urlEvent.fire();
                }
            }

 

 

Invoking Flow From Lightning Component

Introduction

In this blog, I am going to explain how to include a flow in your Lightning component.what I am going to do here is I am creating a Lightning component that will contain flow to create a sales order from the account.

Create a Flow 

Now I am going to create a flow to insert the sales order from the flow screen. We are using two flow elements in this example namely screen and record create elements.Go to flow from the setup menu and click on the new flow . from the Palette drag and drop the screen element into the flow  as shown below

From the add field sections add the different filed to store the sales order data into the object as shown below and click OK after adding all the fields labels

Now drag and drop the records create element flow to insert data into the sales order object. Select the Sales Order object from the Assignments 

perform the field mappings from  the screen input as shown below

Connect the flow element and set the screen element as flow starting element.

Save the flow as shown below and activate the flow.

Create a lighting component 

Go to developer console and create a new Lightning component “Flow Demo”  with the below code.

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId" access="global" >
	<aura:handler name="init" value="{!this}" action="{!c.init}" />
    <lightning:flow aura:id="flowId" />
</aura:component>

And Controller is here below.

({
    init : function (component) {
        // Find the component whose aura:id is "flowId"
        var flow = component.find("flowId");
        // In that component, start your flow. Reference the flow's Unique Name.
        flow.startFlow("SOinser");
    },
})

Understand the code

  • We used <lightning: flow> component to add the flow onto the UI with aura: id.
  • From the controller init method get the flow aura: id of the lightning: flow component to set the flow.
  • Invoke your flow from flow.startFlow(“SOinser”) method of component  .