Chatter in Apexを使ってみよう

皆さんこんにちは、第一ソリューション部の石川です。
本日はChatter in Apexに関する記事となります。

Chatterは社内での情報共有以外にも、
外部のお客様・エキスパートとのコミュニケーションにも使えてとても便利ですね。
新バージョンのリリースがある度に様々な機能が追加されるChatterですが、
ご要望によってはコメントのやりとりだけに機能制限したい場合や、ちょっとUIをいじりたいという方もいらっしゃるかと思います。
今回ご案内差し上げるChatter in Apexを利用することでカスタマイズが可能です。

出来ることはいっぱいあるのですがChatterをカスタマイズするなら間違いなく使うであろう「メンション」と「フィードの取得」に絞って説明していきます。
ちなみにChatter in Apex以外にもConnect APIという名称もありまして、公式ドキュメントでも両方の名称で使われています。

1.メンション

Chatterの標準画面では「@ユーザ名」で出来る機能ですね。
日本語など全角入力の文字では途中で半角スペースを入れないと宛先の候補が出来なかったり、敬称がデフォルトで付かないのでカスタマイズしたい人は多いのではないでしょうか?
今回のサンプルでは選択リストより、ユーザを選択してメンション出来るようにしています。

public with sharing class CustomChatterPageController{

    public String textBody{get;set;}

    public String targetUserId{get;set;}
    public List<Selectoption> users{get; private set;}
    
    public CustomChatterPageController(){
    
        List<Selectoption> options = new List<Selectoption>();
        options.add(new SelectOption('','宛先無し'));
        for(User u:[Select Id,Name From User]){
            options.add(new SelectOption(u.Id,u.Name));
        }
        this.users = options;
        
    }

    public void postFeedItem(){
        
        ConnectApi.FeedItemInput feedItemInput = new ConnectApi.FeedItemInput();
        ConnectApi.MentionSegmentInput mentionSegmentInput = new ConnectApi.MentionSegmentInput();
        ConnectApi.MessageBodyInput messageBodyInput = new ConnectApi.MessageBodyInput();
        ConnectApi.TextSegmentInput textSegmentInput = new ConnectApi.TextSegmentInput();
        
        messageBodyInput.messageSegments = new List<ConnectApi.MessageSegmentInput>();
        
        //メンション先
        if(!String.isEmpty(targetUserId)){
            mentionSegmentInput.id = targetUserId;
            messageBodyInput.messageSegments.add(mentionSegmentInput);
        }
        
        //本文
        textSegmentInput.text = textBody;
        messageBodyInput.messageSegments.add(textSegmentInput);
        feedItemInput.body = messageBodyInput;

        //投稿先
        feedItemInput.subjectId = UserInfo.getUserId();
        
        ConnectApi.FeedElement feedElement = ConnectApi.ChatterFeeds.postFeedElement(Network.getNetworkId(), feedItemInput);
    }
    
}

19行目postFeedItemメソッドからはChatterに投稿する為のフィードアイテムを作成しています。

見慣れないクラスのインスタンスを作っていますが、説明は以下となります。
ConnectApi.FeedItemInput・・・このクラスのプロパティに値を設定してフィードアイテムを作成します。
ConnectApi.MessageBodyInput・・・Chatterの入力部にあたるクラスです、プロパティであるmessageSegmentsは入力用クラスのスーパークラスのListです。31行目、36行目にて画面で入力したメンション先・本文の要素を追加しています。
ConnectApi.MentionSegmentInput・ConnectApi.TextSegmentInput・・・messageSegmentsに追加している要素です。メンション先にはユーザID、本文にはテキストを入れています。

40行目にConnectApi.FeedItemInputのプロパティとしてsubjectIdがありますが、これは投稿先となります。
今回はChatterのデフォルトのフィードに投稿しますのでログインユーザのIDを設定しています。

ConnectApi.FeedItemInputのプロパティに諸々の値を代入したらConnectApi.ChatterFeeds.postFeedElementのメソッドを使い投稿完了です。
引数のNetwork.getNetworkId()は投稿先を組織の中かコミュニティかを指定する為の引数となります。

2.フィードの取得

次はフィードの取得とパースによって必要な情報を抽出する処理となります。
サンプルソースではChatterのデフォルトのフィードを取得していますが、取得用のメソッドはいくつかあるので自由に取得出来ます。
パースのやり方は取得方法が変わっても基本的には同じはずなので参考になるかと思います。
それからフィードとフィードアイテムの違いが分かりにくいかも知れませんが、フィードアイテムが集まったものがフィードと呼ばれています。

サンプルソースは以下のようになっています。

public with sharing class CustomChatterPageController{

    /*
    * フィードを取得
    */
    public List<FeedItemClass> getFeed(){
        List<FeedItemClass> feed = new List<FeedItemClass>();
        
        ConnectApi.FeedElementPage feedElementPage = ConnectApi.ChatterFeeds.getFeedElementsFromFeed(Network.getNetworkId(), ConnectApi.FeedType.News,'me');
        
        for(ConnectApi.FeedElement item:feedElementPage.elements){
            ConnectApi.FeedItem feedItem = (ConnectApi.FeedItem)item;
            if(feedItem != null){
                FeedItemClass feedItemClass = new FeedItemClass(feedItem);   
                feed.add(feedItemClass);
            }
        }

        return feed;
    }

    /*
    * Chatterフィード表示用のクラスです
    */
    public class FeedItemClass{
    
        public String fromUser{get;set;}
        public String toUser{get;set;}
        public String textBody{get; set;}
        public List<feedItemclass> comments{get;set;}
        
        /*
        * フィードの親
        */
        public feedItemclass(ConnectApi.FeedItem feedItem){
        
            this.comments = new List<FeedItemClass>();
            
            //投稿者名
            ConnectApi.User actor = (ConnectApi.User)feedItem.actor;
            this.fromUser = actor.name;
            
            //本文
            List<ConnectApi.MessageSegment> messageSegments = feedItem.body.messageSegments;
            for (ConnectApi.MessageSegment message : messageSegments) {
                setInfo(message);
            }
            
            //コメント
            if (feedItem.capabilities.comments.page.total > 0) {
                List<ConnectApi.Comment> feedCommentItems = feedItem.capabilities.comments.page.items;
                for(ConnectApi.Comment commentItem : feedCommentItems){
                    this.comments.add(new FeedItemClass(commentItem));
                }
            }
        }
        
        /*
        * コメント
        */
        public FeedItemClass(ConnectApi.Comment commentItem){
        
            //投稿者名
            this.fromUser = commentItem.user.displayName;
            
            //本文
            for(ConnectApi.MessageSegment message : commentItem.body.messageSegments) {
                setInfo(message);
            }
        }
        
        /*
        * MessageSegmentをパースして情報をセットします 
        */
        private void setInfo(ConnectApi.MessageSegment message){
            //本文
            if (message instanceof ConnectApi.TextSegment) {
                if(message.text!= null){
                    if(this.textBody== null){
                        this.textBody = message.text;
                    }else{
                        this.textBody+= message.text;
                    }
                }
            }
            // メンション先
            if (message instanceof ConnectApi.MentionSegment) {
                ConnectApi.MentionSegment mentionSeg = (ConnectApi.MentionSegment)message;
                this.toUser = mentionSeg.Name;
            }
        }
    }
}

9行目ConnectApi.ChatterFeeds.getFeedElementsFromFeedでフィードを取得しています。
ここは引数に違和感がありますね。
まずConnectApi.FeedTypeはフィードの種別を表します、サンプルでは「New」でログインユーザが標準のChatterで見れるフィードを表しています。次の引数の'me'ですが、この引数にはどのフィードかを表すIDが本来入ります。例えばレコードのフィードでしたらレコードIDです。
今回はログインユーザのフィードの取得なので'me'を入れる必要がありました。ちなみにユーザIDでもOKです。

次に取得したフィードの要素をパースしています。
75行目のConnectApi.MessageSegmentはフィードアイテムの入力部の要素なのでinstanceofを使って本文かメンションなのかを判別しています。フィードアイテムのプロパティの中にはコメント要素も入っていますので50行目からはコメントをパースしています。

3.動き

殺風景ですが実際のVFはこのようになっています。

2017-01-31_144900.png

文章入力して送信するとこうなります。

2017-01-31_145334.png

実際のChatterだとこうなっています。

2017-01-31_145517.png

4.注意点

気を付けて頂きたいのは、Chatter in Apexで取得できるのはChatterにならった情報になるということです。

2017-01-31_150259.png

             ↓

2017-01-31_150311.png

「すべてのコメントを表示」を押下しないと全部のやり取りが見えない場合は、Chatter in Apexでも同じ取得結果になります。
今回のフィードの取得を画面表示したものが以下となります。1枚目のキャプチャと同じでコメントが3件しか入っていないのが確認出来ます。

2017-01-31_150625.png

勿論、標準のChatterで詳細を見えるということは詳細を取得するメソッドもあります。
今回の記事では紹介していませんが、getFeedElement(communityId,feedElementId) で取得出来ます。
いいね!なども同様にまず省略されて取得されます。

4.まとめ

Salesforceのエンハンスとしてユーザ間のコミュニケーションを促したいという話をよく耳にします。
ただChatterをカスタマイズは手が出にくいイメージが結構あると思うので今回記事にしてみました。
開発の手助けになれば幸いです。

最後に、冒頭にも述べましたがChatterはどんどんバージョンアップしていく機能なのでAPIバージョンにはお気を付け下さい。
本記事ではver.38で作成しています。