sfchatter

Please Note: This is a Proof of Concept integration between ServiceNow and a third party product. This integration is not intended as a complete solution, but rather as a prototype to demonstrate how you could design a similar integration.

We were talking to a prospect the other day, who had an interesting request. Their company already uses Salesforce.com’s Chatter feeds for everything in their company. Every document in salesforce has an associated feed with it. They do not want to move away from Chatter, but they want to introduce ServiceNow into their ITSM strategy. While ServiceNow already features LiveFeed as a way to associate feeds per record or document in the system, the prospect wanted to see the possibility of connecting solely to Salesforce’s chatter feeds for the mean time.

The initial suggestion I had was to integrate the Live Feed feature with Chatter, but they wanted us to demonstrate the capability of doing this without the Live Feed in ServiceNow.

Approach

Salesforce.com allows you configure your instance such that Chatter feeds can be associated with many of their object types. They also provide a good REST-based API for their Chatter feed operations. One thing to note is that their Chatter API does not allow you to create a feed. Instead, you have to create a record that can have a feed associated to it. Once there is a record and associated feed, then the API allows you to perform queries and operations on that feed (such as posting a comment).

Since feeds are required to be connected to some sort of record within Salesforce, I decided to leverage the out of box ServiceNow integration with salesforce.com. This integration sets up a relationship between Salesforce.com’s Case management system and ServiceNow’s incident management system. When cases are created within Salesforce with specific criteria, a corresponding incident is created in ServiceNow.

A "View Chatter Feed" button is added for incidents that are synced with Salesforce.com

A “View Chatter Feed” button is added for incidents that are synced with Salesforce.com

I decided that once this relationship was established through the existing integration, I would add a “View Chatter Feed” button on the incident form. When the user clicks this button, it should make a Chatter API request for the corresponding feed, popup a GlideDialogWindow object, and populate the feed within that window.

Demo

Salesforce.com Configuration Considerations

By default, Chatter feeds are not associated with case records. So you need to go into the Salesforce instance and configure Chatter feeds to attach themselves to cases.

Navigate to App Setup -> Customize -> Chatter -> Feed Tracking to set up the Case Management tool for Chatter feeds.

Navigate to App Setup -> Customize -> Chatter -> Feed Tracking to set up the Case Management tool for Chatter feeds.

Also, the Chatter API uses OAuth2 for authentication, which is different than the standard Salesforce.com API that is used by the OOB integration with ServiceNow. Because of this, you have to set up remote API access to the Chatter feed by setting up a “Remote Access” record through App Setup -> Develop -> Remote Access.

Salesforce.com Remote Access Settings

Salesforce.com Remote Access Settings

Once this Remote Access record is created, the system will provide you with a Consumer Key and Consumer Secret that you must use with your OAuth2 authentication in the REST based API calls to Chatter.

There are a number of OAuth2 methods that the Chatter API supports. For this integration, I was playing under the assumption that not all ServiceNow users would have access to Salesforce.com accounts. Therefore, I chose to use the Username/Password OAuth2 profile and set up the integration to pass in a single set of credentials to Salesforce as an integration user. With this method, you must either append the API Security token to the password, or set up an IP Address trust between Salesforce.com and your IP Address.

To set up a trust between Salesforce.com and your ServiceNow instance, you browse to the "Network Access" link to add your potential IP Addresses.

To set up a trust between Salesforce.com and your ServiceNow instance, you browse to the “Network Access” link to add your potential IP Addresses.

REST Calls to Chatter

There are principally three REST based calls that we are leveraging for this Proof of Concept integration.

Authentication

REST Endpoint: https://login.salesforce.com/services/oauth2/token
Method: POST
Function Parameters:

  • client_id – the Consumer Key provided by the Remote Access settings in Salesforce.com
  • client_secret – the Consumer Secret provided by the Remote Access settings in Salesforce.com
  • username – the Salesforce.com username for the user account we are using as the integration point
  • password – The Salesforce.com password for the user record
  • grant_type – this will be hardcoded to be “password”

Sample Response:

1
2
3
4
5
6
7
{
   "id":"https://login.salesforce.com/id/00Di0000000HtBGEA0/005i0000000DufiAAC",
   "issued_at":"1361510879941",
   "instance_url":"https://na15.salesforce.com",
   "signature":"iI9yLDT9M7iyaEJSSVLRIGo6r1a4HdRE4uv3K93ldwc=",
"access_token":"00Di0000000HtBG!ARoAQEfTnfPw8PIpZKyH.5kY7nilZZHzCLc3Bt5a7AOximyCSAGrIhsBLkP3xfsxCQd1ddJgqa66UGtRkAYIGgm3yW_xSJMp"
}

Retrieving a Feed

REST Endpoint: https://YOUR_PREFIX.salesforce.com/services/data/v27.0/chatter/feeds/record/CASE_RECORD_ID/feed-items
Method: GET
Headers:

  • Authorization : Bearer THE_AUTH_TOKEN_FROM_THE_AUTHENTICATION_RESPONSE

Sample Response:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
{
   "items":[
      {
         "parent":{
            "name":"00001004",
            "id":"500i0000000lT22AAE",
            "motif":{
               "largeIconUrl":"/img/icon/cases64.png",
               "mediumIconUrl":"/img/icon/cases32.png",
               "smallIconUrl":"/img/icon/cases16.png"
            },
            "url":"/services/data/v27.0/chatter/records/500i0000000lT22AAE",
            "mySubscription":null,
            "type":"Case"
         },
         "id":"0D5i00000009vxkCAA",
         "type":"TextPost",
         "clientInfo":null,
         "url":"/services/data/v27.0/chatter/feed-items/0D5i00000009vxkCAA",
         "createdDate":"2013-02-22T05:34:51.000Z",
         "body":{
            "text":"seventeen bottles of sprite on the wall",
            "messageSegments":[
               {
                  "type":"Text",
                  "text":"seventeen bottles of sprite on the wall"
               }
            ]
         },
         "event":false,
         "visibility":"InternalUsers",
         "modifiedDate":"2013-02-22T05:34:51.000Z",
         "photoUrl":"https://c.na15.content.force.com/profilephoto/005/T",
         "canShare":false,
         "comments":{
            "total":0,
            "comments":[

            ],
            "nextPageUrl":null,
            "currentPageUrl":"/services/data/v27.0/chatter/feed-items/0D5i00000009vxkCAA/comments"
         },
         "likes":{
            "total":0,
            "likes":[

            ],
            "nextPageUrl":null,
            "currentPageUrl":"/services/data/v27.0/chatter/feed-items/0D5i00000009vxkCAA/likes",
            "previousPageUrl":null
         },
         "likesMessage":null,
         "isBookmarkedByCurrentUser":false,
         "isDeleteRestricted":false,
         "isLikedByCurrentUser":false,
         "myLike":null,
         "actor":{
            "name":"Mark Masterson",
            "title":null,
            "userType":"Internal",
            "firstName":"Mark",
            "lastName":"Masterson",
            "companyName":"HT4.org",
            "isActive":true,
            "photo":{
               "url":"/services/data/v27.0/chatter/users/005i0000000DufiAAC/photo",
               "largePhotoUrl":"https://c.na15.content.force.com/profilephoto/005/F",
               "photoVersionId":null,
               "smallPhotoUrl":"https://c.na15.content.force.com/profilephoto/005/T",
               "fullEmailPhotoUrl":"https://na15.salesforce.com/img/userprofile/default_profile_200.png?fromEmail=1",
               "standardEmailPhotoUrl":"https://na15.salesforce.com/img/userprofile/default_profile_45.png?fromEmail=1"
            },
            "mySubscription":null,
            "id":"005i0000000DufiAAC",
            "motif":{
               "largeIconUrl":"/img/icon/profile64.png",
               "mediumIconUrl":"/img/icon/profile32.png",
               "smallIconUrl":"/img/icon/profile16.png"
            },
            "url":"/services/data/v27.0/chatter/users/005i0000000DufiAAC",
            "type":"User"
         },
         "attachment":null,
         "originalFeedItem":null,
         "originalFeedItemActor":null,
         "preamble":{
            "text":"Mark Masterson",
            "messageSegments":[
               {
                  "motif":{
                     "largeIconUrl":"/img/icon/profile64.png",
                     "mediumIconUrl":"/img/icon/profile32.png",
                     "smallIconUrl":"/img/icon/profile16.png"
                  },
                  "reference":{
                     "id":"005i0000000DufiAAC",
                     "url":"/services/data/v27.0/chatter/users/005i0000000DufiAAC"
                  },
                  "type":"EntityLink",
                  "text":"Mark Masterson"
               }
            ]
         },
         "topics":null
      }
   ],
   "isModifiedToken":null,
   "nextPageUrl":null,
   "currentPageUrl":"/services/data/v27.0/chatter/feeds/record/500i0000000lT22AAE/feed-items",
   "isModifiedUrl":null
}

Posting to a Feed

REST Endpoint: https://YOUR_PREFIX.salesforce.com/services/data/v27.0/chatter/feeds/record/CASE_RECORD_ID/feed-items
Method: GET
Headers:

  • Authorization : Bearer THE_AUTH_TOKEN_FROM_THE_AUTHENTICATION_RESPONSE
  • Content-Type : application/json

Content Body:

1
2
3
4
5
6
7
8
9
10
11
{
"body" :
   {
      "messageSegments" : [
         {
           "type": "Text",
           "text" : "YOUR_MESSAGE_LINE_GOES_HERE"
         }
      ]
    }
}

Sample Response:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
{
   "parent":{
      "name":"00001004",
      "id":"500i0000000lT22AAE",
      "motif":{
         "largeIconUrl":"/img/icon/cases64.png",
         "mediumIconUrl":"/img/icon/cases32.png",
         "smallIconUrl":"/img/icon/cases16.png"
      },
      "url":"/services/data/v27.0/chatter/records/500i0000000lT22AAE",
      "mySubscription":null,
      "type":"Case"
   },
   "id":"0D5i00000009y6YCAQ",
   "type":"TextPost",
   "clientInfo":{
      "applicationName":"RestTest1",
      "applicationUrl":"null"
   },
   "url":"/services/data/v27.0/chatter/feed-items/0D5i00000009y6YCAQ",
   "createdDate":"2013-02-22T21:38:55.000Z",
   "body":{
      "text":"My first REST based feed post",
      "messageSegments":[
         {
            "type":"Text",
            "text":"My first REST based feed post"
         }
      ]
   },
   "event":false,
   "visibility":"InternalUsers",
   "modifiedDate":"2013-02-22T21:38:55.000Z",
   "photoUrl":"https://c.na15.content.force.com/profilephoto/005/T",
   "canShare":false,
   "comments":{
      "total":0,
      "comments":[

      ],
      "nextPageUrl":null,
      "currentPageUrl":"/services/data/v27.0/chatter/feed-items/0D5i00000009y6YCAQ/comments"
   },
   "likes":{
      "total":0,
      "likes":[

      ],
      "nextPageUrl":null,
      "currentPageUrl":"/services/data/v27.0/chatter/feed-items/0D5i00000009y6YCAQ/likes",
      "previousPageUrl":null
   },
   "likesMessage":null,
   "isBookmarkedByCurrentUser":false,
   "isDeleteRestricted":false,
   "isLikedByCurrentUser":false,
   "myLike":null,
   "actor":{
      "name":"Service Now",
      "title":null,
      "userType":"Internal",
      "firstName":"Service",
      "lastName":"Now",
      "companyName":"HT4.org",
      "isActive":true,
      "photo":{
         "url":"/services/data/v27.0/chatter/users/005i0000000DufiAAC/photo",
         "largePhotoUrl":"https://c.na15.content.force.com/profilephoto/005/F",
         "photoVersionId":null,
         "smallPhotoUrl":"https://c.na15.content.force.com/profilephoto/005/T",
         "fullEmailPhotoUrl":"https://na15.salesforce.com/img/userprofile/default_profile_200.png?fromEmail=1",
         "standardEmailPhotoUrl":"https://na15.salesforce.com/img/userprofile/default_profile_45.png?fromEmail=1"
      },
      "mySubscription":null,
      "id":"005i0000000DufiAAC",
      "motif":{
         "largeIconUrl":"/img/icon/profile64.png",
         "mediumIconUrl":"/img/icon/profile32.png",
         "smallIconUrl":"/img/icon/profile16.png"
      },
      "url":"/services/data/v27.0/chatter/users/005i0000000DufiAAC",
      "type":"User"
   },
   "attachment":null,
   "originalFeedItem":null,
   "originalFeedItemActor":null,
   "preamble":{
      "text":"Service Now",
      "messageSegments":[
         {
            "motif":{
               "largeIconUrl":"/img/icon/profile64.png",
               "mediumIconUrl":"/img/icon/profile32.png",
               "smallIconUrl":"/img/icon/profile16.png"
            },
            "reference":{
               "id":"005i0000000DufiAAC",
               "url":"/services/data/v27.0/chatter/users/005i0000000DufiAAC"
            },
            "type":"EntityLink",
            "text":"Service Now"
         }
      ]
   },
   "topics":null
}

The UI Page

A lot of the viewable magic here is held within the UI Page that displays the Chatter Feed. For the most part, I tried to make the feed inherit the styles of ServiceNow’s Live Feed to keep the look and feed consistent.

The ServiceNow rendered Chatter feed

The ServiceNow rendered Chatter feed

For the proof of concept, I kept the feed to a basic profile picture, Name, and Message. I implemented posting messages, but no liking, commenting, or following.

My UI Page leverages a couple of script includes that I wrote. That code is available in the update set rather than here in the this article.

Here is the UI Page code:

HTML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
<LINK href="/styles/LiveFeedIncludes.cssx?v=08-17-2012_1801" rel="stylesheet" type="text/css"/>

<g:evaluate object="true">
    var chatUtil = new SFDCChatterUtils();
    chatUtil.logHigh("Entering SFDC_Chatter UI Page");
    var view = "page";
    var recordID = RP.getParameterValue("recordID");
    if(recordID==""){
      var recordID = RP.getWindowProperties().get('recordID');
      view="dialog";
    }
    chatUtil.logHigh("recordID: " + recordID);
    if(recordID){
      chatUtil.logHigh("view: " + view);
 
      var feed = chatUtil.getRecordFeed(recordID);
      var feedSize = feed.items.length;
      var itemCounter = 0;
      chatUtil.logHigh("FeedSize: "+feedSize);
      if(chatUtil.verbosity == "High"){
          //JSUtil.logObject(feed);
      }
    }
  </g:evaluate>
 
  <j:if test="${recordID!='' &amp;&amp; feedSize!=null}">
    <div style='padding:20px;'>
      <div id="live_feed_input_div" style="">
        <form id="live_feed_form" name="live_feed_form" action='/SFDC_Chatter.do' method='POST'>
          <div style="padding-right: 4px;">
            <textarea id="live_feed_input" name="message" class="live_feed_input" data-prompt="What are you working on today?" style="resize: none; overflow-y: hidden; height: 28px; color: rgb(102, 102, 102); opacity: 0.9;"></textarea>
            <textarea class="live_feed_input" data-prompt="What are you working on today?" style="color: rgb(102, 102, 102); opacity: 0.9; resize: none; overflow-y: hidden; position: absolute; visibility: hidden; top: 0px; left: -9999px; width: 476px;" tabindex="-1"></textarea>
          </div>

          <table style="width: 100%; padding: 0; position:relative; margin-top: 5px;">
            <tbody>
              <tr>
                <td style="text-align: right" width="90%">
                  <input type='button' onclick="addComment('${recordID}')" value="Update"/>
                </td>
              </tr>
            </tbody>
          </table>
        </form>
      </div>
     
      <j:while test="${itemCounter!=feedSize}">
        <j:set var="jvar_actor" value="${feed.items[itemCounter].actor.name}"/>
        <j:set var="jvar_createdDate" value="${feed.items[itemCounter].createdDate}"/>
        <j:set var="jvar_message" value="${feed.items[itemCounter].body.text}"/>
        <j:set var="jvar_avatar" value="${feed.items[itemCounter].photoUrl}"/>
   
        <div class="live_feed_message ">
       
          <div class="live_feed_avatar">
            <div class="live_feed_member_avatar live_feed_avatar_profile_img live_feed_follow_buttons">
              <div class="live_feed_avatar_img" style='background-image:url("/images/profile/buddy_default.pngx");padding-top:0px;padding-bottom:0px;'>
                   <img class="live_feed_avatar_img" src="${jvar_avatar}" style='width:48px;height:48px;'/>
              </div>
            </div>
          </div>
       
       
         <div class="live_feed_message_text">
           <span class="live_feed_by">${jvar_actor}</span>: ${HTML:jvar_message}
         </div>
         <div class="live_feed_message_date"> <span title="${jvar_createdDate}">${jvar_createdDate}</span>
         </div>
         <j:set var="itemCounter" value="${itemCounter+=1}"/>
        </div>
     
      </j:while>
    </div>
  </j:if>
</j:jelly>

Client Script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function addComment(recordID){
   //Do an ajax call to post the Message then...
   var message = document.getElementById("live_feed_input");
   var name = g_user.firstName+" "+g_user.lastName;
   var ga = new GlideAjax('SFDCChatterAjax');
   ga.addParam('sysparm_name', "ajaxPostFeedItem");
   ga.addParam('sysparm_recordID',recordID);
   ga.addParam('sysparm_message',"("+name+") "+message.value);
   ga.getXMLWait();
   //alert("message: "+message.value);
   
   //Refresh the dialog
   GlideDialogWindow.get().render();
   
}

//Allow the feed to automatically display new feed entries from external sources
setTimeout(function(){
   var textarea = document.getElementById("live_feed_input");
   if(!textarea.value || textarea.value==" "){
    GlideDialogWindow.get().render();
   }
}, 5000);

Setup and Configuration

If you want to take this integration out for a spin, you just need to follow these steps to get it up and running.

  1. Enable the Salesforce.com Integration plugin in your ServiceNow instance. You many need to request that Support enable it for you if you don’t see it listed in the Plugins list.
  2. Configure and test the Salesforce.com integration to ensure it is functioning properly. Use the wiki documentation as a guide: Salesforce.com Integration Documentation.
  3. Install the Update Set included at the end of this article.
  4. Set up a Remote Access account in Salesforce.com for your instance (explained earlier in this article).
  5. Set up Chatter feed to track the Case Management data in Salesforce.com (explained earlier in this article).
  6. (optional) Set up Salesforce to trust your instance IP address (explained earlier in this article).
  7. Go to the ServiceNow application for “Integration – Salesforce.com” and configure the additional properties created by this update set: Sales Force Instance Prefix, Salesforce Consumer Key, Salesforce API Consumer Secret, Chatter Log Verbosity Level.

The Update Set

Salesforce.com Chatter integration update set