LoginSignup
8
1

More than 5 years have passed since last update.

Quip API をためしてみる

Last updated at Posted at 2016-12-16

Quip について

一言で言えば、"文書管理アプリ"です。
が、他のアプリケーションと異なるポイントとしては、"コミュニケーション"と"文書を一緒に作り上げていく"を同時に実現することを目指したもの...だと思います。

また、以下のような特徴を持っています。

  1. 全てのドキュメントにチャット機能があり、チーム内でコミュニケーションをとりながら文書を作り上げていける
  2. モバイルからでも文書を編集したり、チャットに参加してコミュニケーションとれる(モバイルファースト!)
  3. 文書の特定の箇所でコメントを追記できる(チームメンバへのメンションも可)
  4. 文章の他に、スプレッドシート、チャートをつけられる

さて、そんなQuipですが、今年になってSalesforceが買収したことを発表してます。Dreamforce2016でも大きく取り上げられており、今後Salesforceとの連携が強化されていくことでしょう。

なお、Dreamforce2016におけるQuipに関するセッションは、以下で公開されています。
* https://www.youtube.com/watch?v=DduJWrkCpsY
* 製品デモ -> 19:42〜

Quip API

そして、QuipにもAPIリファレンスが公開されています。
-> https://quip.com/api/reference

Quip API のクライアントとしてPython, node.jsも実装されているようです。
-> https://github.com/quip/quip-api

APIリファレンスは以下の構成になっています。

  • Authentication
  • Threads
  • Messages
  • Folders
  • Users

Quipでは、"Thread"の中にドキュメントとメッセージ(チャットの履歴)をもっていて、これがメインのリソースとなっています。
Quipの"Folder"は、ファイルシステムにおけるディレクトリというよりも、タグ付けの役割を担っているようです。1つのTreadは複数の"Folder"と関連づけることができるようです。

Salesforceから使ってみる

さて、APIが公開されている...ということで、SalesforceからもHttpCalloutを使えば色々と操作できることでしょう、というわけで(わざわざ)試してみました。

認証

認証では "Personal Authentication" と "Domain Authentication" の2種類が用意されています。"Domain Authentication" はエンタープライズ版で使えるそうなので、 "Personal Authnecation" を使います。

"Personal Authencation" では、https://quip.com/api/personal-token
にアクセスして、トークンを取得します。
ただ、curlでアクセスして...というより、ここだけはブラウザでアクセスして、"Get Personal Access Token"というボタンでアクセストークンを発行するようです。

quip_get_personalaccesstoken.png

取得したトークンは...一旦どこかに保持しておく必要があるので、例えばカスタム設定とかに置いておきます。
※ここは非常に残念で、今後要改善ですね。。

customsetting_quip_token.png

APIを利用する

ここまでくればHttpCalloutを使ってQuipのAPIを利用する感じになります。

今回は以下の流れに沿ったプログラムを考えてみます。

  1. 取引先を作成したら、その名前でフォルダを作成する
  2. フォルダ作成と同時に、ドキュメントも作成する

※以下は、取引先作成後に、Quipフォルダ/ドキュメント作成のためのバッチをスケジューラーに登録してみる、という動きになります。
別にバッチでなくてもfutureアノテーションのついたメソッドを用意しても良いです。

AccountToQuipTrigger.trigger
trigger AccountToQuipTrigger on Account (after insert) {


    List<Id> objIds = new List<Id>(Trigger.newMap.keySet());

    BatchCreateQuip batch = new BatchCreateQuip(objIds);
    System.scheduleBatch(batch, 'create_quip_docs', 1, 1);

}
BatchCreateQuip.cls
global class BatchCreateQuip implements Database.Batchable<sObject>,Database.AllowsCallouts,Database.Stateful {

    private List<Id> objIds;
    private String objApi;

    global BatchCreateQuip(List<Id> pIds){
        objIds = pIds;
        objApi = objIds[0].getSObjectType().getDescribe().getName();
    }

    global Database.QueryLocator start(Database.BatchableContext BC) {
        String soql = 'Select Name From ' + objApi + ' Where Id in :objIds Order By Name';
        return Database.getQueryLocator(soql);
    }

    global void execute(Database.BatchableContext BC, List<sObject> scope) {

        List<SObject> upds = new List<SObject>();
        for(SObject obj : scope){
            obj = ServiceCreateQuip.createFolder(obj);
            obj = ServiceCreateQuip.createDocument(obj); 

            upds.add(obj);
        }
        update upds;

    }

    global void finish(Database.BatchableContext BC) {
        //
    }

}
ServiceCreateQuip.cls
global with sharing class ServiceCreateQuip {

    global ServiceCreateQuip(){}

    /**
     *
     */
    global static SObject createFolder(SObject obj){
        String url = 'https://platform.quip.com/1/folders/new';
        String method = 'POST';

        Map<String, String> param = new Map<String, String>{
            'title' => String.valueOf(obj.get('Name'))
        };

        String body = convertParamToQueryString(param);


        HttpResponse res = sendRequest(method, url, body);


        if(res.getStatusCode() != 200){
            throw new ServiceCreateQuipException(res.getBody());
        }

        Map<String, Object> m = (Map<String, Object>)JSON.deserializeUntyped(res.getBody());
        Map<String, Object> f = (Map<String, Object>)m.get('folder');

        String quipId = String.valueOf(f.get('id'));

        obj.put('QuipFolderId__c', quipId);
        return obj;
    }

    /**
     *
     */
    global static SObject createDocument(SObject obj){
        String url = 'https://platform.quip.com/1/threads/new-document';
        String method = 'POST';

        Map<String, String> param = new Map<String, String>{
            'content' => String.format(
                            '<h1>{0}</h1>',
                            new List<String>{
                                String.valueOf(obj.get('Name'))
                            }
                        ),
            'title' => String.valueOf(obj.get('Name'))
        };
        if(obj.get('QuipFolderId__c') != null){
            param.put('member_ids', String.valueOf(obj.get('QuipFolderId__c')));
        }

        String body = convertParamToQueryString(param);


        HttpResponse res = sendRequest(method, url, body);


        if(res.getStatusCode() != 200){
            throw new ServiceCreateQuipException(res.getBody());
        }

        Map<String, Object> m = (Map<String, Object>)JSON.deserializeUntyped(res.getBody());
        Map<String, Object> f = (Map<String, Object>)m.get('thread');

        String quipId = String.valueOf(f.get('id'));

        obj.put('QuipThreadId__c', quipId);
        return obj;
    }

    /**
     *
     */
    private static String convertParamToQueryString(Map<String, String> p){

        List<String> wks = new List<String>();
        for(String key : p.keySet()){
            String wk = String.format(
                '{0}={1}',
                new List<String>{
                    key,
                    EncodingUtil.urlEncode(p.get(key), 'UTF-8')
                }
            );
            wks.add(wk);
        }

        return String.join(wks, '&');
    }

    /**
     *
     */
    global static HttpResponse sendRequest(String method, String url, String body){
        HttpRequest req = new HttpRequest();

        //
        String token = CustomSetting.getQuipToken();

        req.setEndpoint(url);
        req.setHeader('Authorization', 'Bearer ' + token);
        req.setMethod(method);
        req.setBody(body);

        Http h = new Http();
        HttpResponse res = h.send(req);

        return res;
    }


    global class ServiceCreateQuipException extends Exception {}
}

おためし

取引先を作成してみます。

create_account.png

(作成後)
sfdc_account.png

(Quipのリンク起動)
quip_doc.png

無事にフォルダとドキュメントが作成されました。

その他

12/13-14に開催された"Salesforce World Tour Tokyo 2016"でもQuipのブースはもちろんありました。
そこでSalesforceとのインテグレーションについても少し話を聞くことができました。

※予めお伝えしますと、まだSalesforce傘下に入って間もないこともあり、インテグレーションに関しては発展途上...という話でした。

  • SalesforceのLightningComponentとして組み込む
  • 関連リストとしてQuipへのドキュメントリンク用レコードを保持する
    • 関連リストにボタンを用意して、Quipのドキュメントを作成し、そのリンクをレコードとして保持する

といったことが今後追加されるようです。

おわりに

リアルタイムでコラボレーションしながらドキュメントを作成orメンテしていくという点では、Quipはなかなか使い勝手が良さそうですし、様々なところで応用ができそうかな、と思います。

開発視点からすれば、APIが公開されているので、Salesforceもしくは他のクライアントアプリからQuipを色々と操作することもできるようです。

今後、Salesforceとの連携がますます強化されていくとは思いますが、HttpCalloutを使って色々と操作することもできるかと思います。(どんな連携機能が今後実装されていくかは....分かりませんが。。。)
Salesforceにはない文書機能について、その可能性を探ってみるのも面白いかもしれません。

8
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
1