Syncing Chatter Posts on Case Feed with Case Comments

UPDATE June 21, 2015: If anyone wants to install the components outlined on this post you can do so by using the un-managed package via the below link. Note that you may need to make modifications to make it work within your org (such as any existing triggers that may cause conflict). Please test it from a sandbox or developer edition org beforehand.

Sandbox:https://test.salesforce.com/packaging/installPackage.apexp?p0=04t1a00000066gC

UPDATE January 30, 2019: I have pushed all apex classes, triggers, and flow definition to a repository on Github.

My first blog post made it to the SFDC Goodies List for May 2015! Thanks to Elna Miller for the inclusion and shout out. There are plenty of great resources on the list so please check it out.

As a second post I would like to follow-up my first post by experimenting with syncing Chatter posts on a Case record feed to Case Comments. It is great to have users be able to post to the feed and have case comments be automatically added to the feed. I find it to be confusing to have a case comment posted to the feed and have someone respond via Chatter. The person who is handling the case may need to track communication from both the feed and case comments which is in my opinion, not that efficient. Case comments are added to the feed already, but since Chatter is such a quick way to communicate with users, why not sync posts to the feed with case comments, allowing one to find responses no matter if you are a Chatter expert or would like to stick with traditional case comments.

So here are the actions I want to achieve for this experiment:

  1. Create a case comment for any chatter post added to the Case feed.
  2. Remove any duplicate feed items as a result of insertion of case comments via Chatter posts (case comments are added to the feed).
  3. Create a case comment for any feed comments added to the Case feed.

Creating a Case Comment from Chatter Post

As I have done so from my previous post, I will be utilizing Lightning Process Builder for parts of this experiment to start off actions. I created a Lightning Process Builder flow against the Feed Item object checking if the Feed Item’s parent id starts with ‘500’ which will indicate that it is part of a Case.

LPB1

Create a Record - Case Comment

As I configured the action for the Process Builder flow I encountered a slight road block…Case Comment is not available from the Create a Record action. So, here was an opportunity to use the Apex option and the InvocableMethod annotation.

I have also posted an Idea to allow the ability to create a Case Comment record via Process Builder as well. Please up-vote if you think this would be an useful addition.

In order to use this option I had to create an Apex class with Invocable Variables and a method using the InvocableMethod annotation so that it can be used via the Process Builder flow.

global class CaseFeedCommentSyncUtils {

    final static String COMMENTINSERT = 'FeedItem2Case';
    final static String FEEDREMOVAL = 'CaseCommentFeedItemDeletion';

    global class SyncParameter{
        @InvocableVariable(Label = 'Sync Type'
                          Description = 'Indicates Feed sync or feed item removal'
                           Required = true)
       	global String syncType;

        @InvocableVariable(Label = 'Feed Body'
                          Description = 'Body of the chatter post')
       	global String feedBody;

        @InvocableVariable(Label = 'Parent Id'
                          Description = 'Id of parent record, feed item or case comment'
                           Required = true)
       	global String parentId;

    }

    @InvocableMethod
    global static void chatterFeedToCaseComment(List syncParams){
        if(syncParams[0].syncType == COMMENTINSERT){
            String feedBody = syncParams[0].feedBody;
            String parentId = syncParams[0].parentId;
            CaseComment cc = feedBody != null && parentId != null ?
                new CaseComment(CommentBody = feedBody + + '\n\n' + ' Case Comment from Feed', ParentId = parentId)
                : null;

            if(cc != null){
                try{
                    insert cc;
                }catch(Exception caseCommentInsEx){
                    system.debug(LoggingLevel.ERROR, 'Case Comment Insert Error: ' + caseCommentInsEx.getMessage());
                }
            }
        }
    } 

}

The class has a nested class called SyncParameter with member global variables that will be used to store parameters that will be used in the sync functionality.

  • syncType : A string variable that will store the type of sync action (more on the different sync actions later)
  • parentId : A string variable that will store the record Id value of the parent Case.
  • feedBody : A string variable to store the body of the Chatter Post on the Case feed.

The method chatterFeedToCaseComment takes a list of type SyncParameter as an argument. If the syncType variable equals to ‘FeedItem2Case’, a new CaseComment object is created with parentId and feedBody stored as CaseComment.ParentId and CaseComment.CommentBody with the  text ‘Case Comment from Feed’ appended to the body to indicate the comment is from a chatter post. The case comment is then inserted as a record.

In order to call the method I configured the Process Builder action to call Apex, selecting the FeedToCaseCommentUtils class. In the variables sections, I set the follow:

  • Parent Id : FeedItem.ParentId
  • Sync Type : “FeedItem2Case”
  • Feed Body : FeedItem.Body

LPB Action 1

Duplicate Case Feed ItemsThe Process Builder flow will execute the Invocable Apex method that creates a case comment on the same case record using the Chatter Post body as the comment body. This will, however, create duplicate chatter post as the case comment may cause a Case Comment Post to be created referencing the newly inserted comment. As this may be confusing I wanted to find a way to remove this potential duplicate post, which leads to the second part of this experiment.

Removing Duplicate Chatter Posts

My first thought to remove the duplicate chatter post was to use the same Process Builder flow to execute an action on Feed Item records created for the Case feed of type “Case Comment Feed” and call an Apex method to delete the Feed Item record. The problem with that option was that Case Comment Chatter Posts on the Case feed did not trigger the Process Builder flow. Instead I created a second Process Builder flow against the Case Comment object.

Case Comment LPB Criteria

The only criteria is check if the CaseComment.CommentBody field contains the text “Case Comment from Feed” which is appended to every Case Comment record that was synced from a Case feed Chatter post. In order to handle the deletion of the Feed Item record I added the following to the chatterFeedToCaseComment method.

@InvocableMethod
    global static void chatterFeedToCaseComment(List syncParams){
        if(syncParams[0].syncType == COMMENTINSERT){
            String feedBody = syncParams[0].feedBody;
            String parentId = syncParams[0].parentId;
            CaseComment cc = feedBody != null && parentId != null ?
                new CaseComment(CommentBody = feedBody + + '\n\n' + ' Case Comment from Feed', ParentId = parentId)
                : null;

            if(cc != null){
                try{
                    insert cc;
                }catch(Exception caseCommentInsEx){
                    system.debug(LoggingLevel.ERROR, 'Case Comment Insert Error: ' + caseCommentInsEx.getMessage());
                }
            }
        }
        else if(syncParams[0].syncType == FEEDREMOVAL){
            List delFeedIds = new List();
        	ConnectApi.FeedElementPage feedPage =
            Connectapi.ChatterFeeds.getFeedElementsFromFeed(null, ConnectApi.feedtype.Record, syncParams[0].parentId);

        	for(ConnectApi.FeedElement elem : feedPage.elements){
            	if(elem.capabilities != null){
                	if(elem.capabilities.caseComment != null){
                    	String caseCommentTxt = elem.capabilities.caseComment.text != null ? elem.capabilities.caseComment.text : '';

                    	if(caseCommentTxt.contains('Case Comment from Feed')){
                        	String currPageUrl = elem.capabilities.chatterLikes.page.currentPageUrl;
                        	List urlSplit = currPageUrl.split('/');
                        	delFeedIds.add(urlSplit[6]);
                    	}

                	}
            	}
        	}

        try{
            List delRes = !delFeedIds.isEmpty() ? Database.delete(delFeedIds) : null;
        }catch(DMLException feedDelEx){
            system.debug('error during feed item deletion: ' + feedDelEx.getMessage());
        }
        }
    }

I have added an additional condition in the method to check if the syncType parameter equals to “CaseCommentFeedItemDeletion”.  To pull the feed elements from the case feed I will the use getFeedElementsFromFeed method which is part of the ChatterFeeds class from the ConnectApi namespace. The method takes three arguments:

  • communityId : I am passing a null value in this instance
  • feedType : This is takes a value of type FeedType. I am passing ConnectApi.FeedType.Record since I need to pull the feed elements from a Case record feed.
  • subjectId : This will be the record’s Id so I will be using the parentId from the SyncParameter class.

As the method returns a list of Feed Element objects from the Case I need to loop through the list and identify if the feed element is from a case comment and has the text “Case Comment from Feed”. If it does I add the Feed Item record Id to a list of type Id and delete the records by calling the Database.delete method. Here is a summary of how I check if the feed element needs to be removed.

  • If the feed element is a Case Comment Feed item the Capabilities object will not be null.
  • The caseComment variable from Capabilities will not be null and and will contain the text “Case Comment from Feed”.
  • In order to retrieve the Feed Item record Id, I store capabilities.chatterLikes.page.currentPageUrl to a String variable and split the value with the forward slash (‘/’) character. The record Id value is stored in the 7th index of the split values.
  • I spent some time familiarize myself to the components of the ConnectApi via the help docs to understand which fields I need to check.
  • I would recommend printing the contents of the Feed Elements in system.debug to see the values being returned. This gave me better context of which vales I needed were stored in which fields.

Case Comment Removal Action

The last item for the removal of duplicate Chatter posts is to add the Apex action to the Process Builder flow. I set the the Case Commient’s Parent Id to SyncParameter.parentId and set the text value “CaseCommentFeedItemDeletion” to SyncParameter.syncType.

This completes the duplicate chatter post removal section. Finally, the last part will be to sync Chatter comments to Case comments as well. Unfortunately, Process Builder does not support the Feed Comment object at this time so I had to resolve to using a good old fashioned Apex Trigger to facilitate the task. I have added an Idea to add support for Feed Comment please up-vote if you agree with this functionality.

Creating a Case Comment from Chatter Post

Since I was not able to use Process Builder to to sync feed comments as case comments I created an Apex trigger against the Feed Comment object to handle the task. I would also like to note that if you do not want to have all comments of a Chatter post to be a Case comment as well then simply just skip this part. It made sense to me in scenarios when you add a case comment and someone replies by commenting on the case feed.

trigger FeedCommentBeforeTrigger on FeedComment (before insert, before update, before delete) {
    if(Trigger.isInsert){
    	List caseComments = new List();
        for(FeedComment feedCom : trigger.new){
        	String parentId = (String)feedCom.ParentId;

        	if(parentId.startsWith('500')){
      			CaseComment cc = new CaseComment(ParentId = feedCom.ParentId, CommentBody =
                                                 feedCom.CommentBody + '\n\n' + 'Case Comment from Feed');
         		caseComments.add(cc);
        	}
    	}

    	try{
      		List insRes = !caseComments.isEmpty() ? Database.insert(caseComments) : null;
    	}catch(DMLException commentInsEx){
        	system.debug('Error during case comment insertion: ' + commentInsEx.getMessage());
    	}
    }
}

The above trigger code iterates through newly created feed comment records and checks if the parent id of the record is from the Case feed. If so, a case comment object is created and added to a list of type Case Comment. I insert the case comments if any of the feed comment records match the criteria. To  simplify matters I have placed all the necessary code in the trigger, however, I would recommend moving the bulk of the logic involved into an Apex class method instead.

Examples

Chatter Post 1

Let’s test the changes. Here is an user posting to the Chatter feed of a case record. The same chatter post is now added as a case comment via the Feed Item Process Builder flow and the Apex method that inserts the case comment record,

Case Comment from Chatter Post 1

A response is made via case comment and the case comment feed item is further then commented on by the user which is then synced as a case comment.

Response via Case Comment

Feed Comment Response on Case Comment

Note that the feed does not have the case comment feed that should have been created by syncing the original chatter post as a case comment. The removal is handled by the Case Comment Process Builder flow and the Apex method action that delete that specific feed item record.

Feed Comment to Case Comment

The feed comment is now synced as a case comment via the Apex trigger.

This concludes my experiment. Please let me know what you think and if you found it helpful.

Thanks for reading!

9 thoughts on “Syncing Chatter Posts on Case Feed with Case Comments”

  1. Did you have any test classes that you wrote for this so that it can comply with the code coverage requirements?

    Like

  2. Great post, quick question – if I want to call flow before posting message how can I send email to group? if members respond to email reply how can we add that comment to case chatter feed?

    Like

    1. I would look into using Process Builder to post to chatter and @ mention specific users or a chatter group. If the group members are configured to receive email alerts each they are mentioned or their group is mentioned they can reply to the email directly and their replies will be posted onto the feed. Check out this posts for a good overview of using process builder or visual workflow to post to chatter.
      http://www.jdope.com/blog/mention-with-salesforce-process-builder/

      Like

  3. This is awesome. Thanks for providing the details and Unmanaged Package.

    I’m noticing that when creating a feed post, I’m getting HTML tags in the Case Comment

    For example
    Feed
    Feed post.

    Comment made?

    Case Comment:
    Feed post. Comment made?

    Case Comment from Feed

    Any suggestions on how to automatically remove the HTML tags?

    Like

    1. Brandon,

      Based on my cursory glance at the code.. In the apex method `CaseCommentPost.chatterFeedToCaseComment` when assigning `new CaseComment( CommentBody = feedBody, … )` then you would need to use `feedBody.stripHtmlTags()` to remove any rich-text formatting. That also removes any newlines. If you wanted to preserve newlines preserved you might try `feedBody.replace(”,’\n\n’).stripHtmlTags()`

      Doug

      Like

  4. I realize this thread is very old, but the problem I’m having with the code related to this is that when a Chatter comment is made, it creates two feed items right away: 1. for the Chatter comment and 2. for the Case comment. However, if someone makes another Chatter comment, then it deletes the original Chatter comment but still creates two feed items: 1. Chatter comment and 2. Case Comment. Any ideas?

    Like

  5. Hi Terrance,
    I know this is an older post, but it addressed an issue I had been facing perfectly.
    Is there an alternative location to obtain the test classes for deployment? I cannot get through using the link at the top of the article.

    regards
    Jon

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.