Preserving Mentions in Triggers

One of the most requested Chatter features in Summer ’15 is the ability to edit posts and comments. While it’s an awesome feature in its own right, I discovered a delightful side effect while testing its API: you can write just a few lines of Apex code to preserve @mentions in triggers.

The Problem: Mentions Disappear in Triggers

Apex triggers are a powerful part of the Salesforce1 platform. One way to customize Chatter is to write a trigger that modifies the body of a feed item or comment when it’s posted.

For example, compliance apps that prevent certain words or phrases from being posted do exactly that: they substitute blacklisted words with replacement text like “*****”.

The drawback is that if there’s an @mention, it gets “stripped out” and displayed as plain text—the person or group you’re mentioning doesn’t get notified, which is a serious limitation.

To see the problem, let’s create a simple trigger that adds a disclaimer at the end of each post:

trigger AddDisclaimer on FeedItem (before insert) {
    for (FeedItem f : trigger.new) {<
      f.body += '\n\n---\nDisclaimer: Modified in a trigger.';
        }
}

Before activating the trigger, a post with an @mention looks like this:

blog_post_no_trigger.png

After activating the trigger to add the disclaimer, the @mention gets stripped out:

blog_post_no_mention_with_trigger.png

This has been a longstanding issue for admins and developers who want to customize Chatter for their organizations.

The root cause of the problem is that @mentions aren’t supported in the FeedItem and FeedComment sObjects. The only ways to use @mentions programmatically are with the Chatter REST API and the methods in the ConnectApi.ChatterFeeds Apex class. Until Summer ’15, there wasn’t a way to use the Apex methods to solve the trigger problem.

The Solution: Editing + ConnectApi = Mentions Preserved!

What I discovered is that you can call the following Apex methods from within an insert trigger to preserve @mentions:

ConnectApi.ChatterFeeds.updateFeedElement()

ConnectApi.ChatterFeeds.updateComment()

Prerequisites

In order for this to work, the “Allow users to edit posts and comments” Chatter setting must be on, and the “Edit my own posts” user profile permission must be enabled for all users who post to Chatter. This is very important. Any users who don’t have permission to edit their own posts will see errors when they attempt to post.

Create a Trigger

Add an after insert trigger on FeedItem (or FeedComment, if you want to edit comments). The trigger delegates to a helper class.

trigger AddDisclaimerTrigger on FeedItem (after insert) {

     EditFeedItemHelper.edit(trigger.new);

}

Create an Apex Helper Class

The helper class does all the work:

  1. Makes a call to ConnectApi.ChatterFeeds.getFeedElementBatch() to get all the feed items passed into the trigger. It does this to get the message segments, which include any mention segments.

  2. For each feed item:

    1. Convert all the body’s message segments into input segments. (Tip: you can use the createFeedItemInputFromBody() method I added to my ConnectApiHelper class.)

    2. Modify the input segments as you see fit.

    3. Call ConnectApi.ChatterFeeds.updateFeedElement() with the modified input segments to update the feed item.

Here’s sample code for the helper class. Note that it depends on the ConnectApiHelper class that’s available on GitHub.

public class EditFeedItemHelper {

  /**

  * Call this method from an after insert FeedItem trigger.

  * It updates the feed items passed in and preserves @mentions.

*/

  public static void edit(FeedItem[] feedItems) {

    String communityId = Network.getNetworkId();

    List<String> feedItemIds = new List<String>();

    for (FeedItem f : feedItems) {

        feedItemIds.add(f.id);

    }

        // Get all feed items passed into the trigger (Step #1).

    ConnectApi.BatchResult[] results = ConnectApi.ChatterFeeds.getFeedElementBatch(communityId, feedItemIds);

    for (ConnectApi.BatchResult result : results) {

        if (result.isSuccess()) {

            Object theResult = result.getResult();

            if (theResult instanceof ConnectApi.FeedItem) {

                ConnectApi.FeedItem item = (ConnectApi.FeedItem) theResult;

                // Convert message segments into input segments (Step #2a).

                ConnectApi.FeedItemInput input = ConnectApiHelper.createFeedItemInputFromBody(item.body);

                // Modify the input segments as you see fit (Step #2b).

                modifyInput(input);

                // Update the feed item (Step #2c).

                // We need to update one feed item at a time because there isn't a batch update method yet.

                ConnectApi.ChatterFeeds.updateFeedElement(communityId, item.id, input);

            }

            else {

                // Do nothing. Posting other feed element types isn't supported.

            }

        }

        else {

                System.debug('Failure in batch feed element retrieval: ' + result.getErrorMessage());

        }

    }

}

    /**

    * Update the feed item input here!

    */

    public static void modifyInput(ConnectApi.FeedItemInput input) {

        // This example appends text to the feed item.

        ConnectApi.TextSegmentInput textInput = new ConnectApi.TextSegmentInput();

        textInput.text = '\n\n---\nDisclaimer: Modified in a trigger.';

        input.body.messageSegments.add(textInput);

    }

}

Since you call ConnectApi.ChatterFeeds.updateFeedElement() within the same transaction as the trigger, you see the changes immediately in the UI—there’s no need for a refresh.

Here’s the feed item that we showed you earlier, with a disclaimer added by a trigger that uses this technique. Notice that the timestamp says “Edited Today” because we edited it in the trigger. The @mention is preserved:

mention_preservation.png

This is a simple example, but you can implement anything you like in your modifyInput() method. Want to always capitalize your company’s name? Prevent certain words from being posted? Add a hashtag whenever a certain word appears? It’s up to you.

Note that we haven’t included this technique in our documentation because we don’t offer official support for it. As with all triggers, the order in which the trigger gets fired among other after insert triggers (including those installed by AppExchange apps) is not predictable. Please keep this in mind when using this technique.

We hope this helps to improve your Chatter customizations!

Published
June 30, 2015
Topics:

Leave your comments...

Preserving Mentions in Triggers