GaroonにもJavaScirpt APIが実装されて久しいですが、みなさんお使いでしょうか。特に2017年11月のアップデートでイベントや情報取得のJavaScript APIが多数追加され、なかなか充実を見せています。

先日「Garoonのワークフロー承認に合わせて、kintoneのレコード登録する」というものを試してみた内容を書き下ろしていきます。トラップありで長編になりますが最後までお付き合いくださいm(__)m

Garoon -> kintone連携

GaroonにはもともとSOAP APIが備わっていましたが、「Garoonでワークフローが承認されたら・・・」、「Garoonでスケジュールが登録されたら・・・」といったイベントトリガーに合わせたロジックの発動ができない状況にありました。しかし、JavaScritp APIの実装でこの辺ができるようになってきました

連携手法の選択肢

具体的な連携方法ですが、Garoonからkintoneを操作するには、Garoon JavaScript APIを駆使してkintoneのREST APIが操作できれば良いわけです。現状での選択肢は「XMLHttpRequest」Garoonから外部のAPIを実行する機能の2択になりそうです。

前者は同一ドメインへのリクエストに限られ、POST、PUT、DELETEの際にはkintoneのCSRFトークンが必要になりますが、Garoon側でkintoneCSRFトークンを取得できるAPIがありますので、心置きなくXHRでリクエストを送れます。

後者はkintoneでいう「kintone.plugin.app.proxy()」のイメージに近く、管理画面で行うプロキシAPIの設定と協調してGaroonのJavaScriptカスタマイズから外部のAPIを呼び出す機能です。他のドメインのkintoneへのアクセスも可能になってきます(もちろんkintone以外にもAPIの要件が合えば別のサービスにもアクセスできます)。

Garoonのワークフロー承認に合わせて、kintoneのレコード登録する

実際にやっていきたいと思います。ワークフロー承認時にkintoneにレコード登録するというTipsが既にあって、それをそのまま試しても面白くないので、Garoonのワークフローとkintoneのアプリを次の通り新しく準備したいと思います。

「社内で資格取得をGaroonで申請して承認されると、kintoneに登録される」というシナリオです。祝金の支給決裁の意味を伴う部分にはGaroonのワークフローを利用しつつ、kintoneに入れることで資格取得者の管理に生かすというようなものを想定しました。

Garoonのワークフロー設定

まず、Garoonワークフローの設定です。

項目名 項目タイプ 項目コード
kintone資格試験登録 文字列(1行)(標準項目) kintone_certification
新規/更新種別 メニュー type
kintone認定 アソシエイト チェックボックス kintone_associate
kintone認定 アプリデザインスペシャリスト チェックボックス kintone_app_design_specialist
kintone認定 カスタマイズスペシャリスト チェックボックス kintone_customization_specialist

kintoneアプリの設定

次に、kintoneのアプリ(フィールド)の設定です。

フィールド名 フィールドコード フィールド値 フィールドタイプ
取得者 取得者 ユーザー選択/USER_SELECT
種別 種別 新規/更新 ドロップダウン/DROP_DOWN
取得資格 取得資格 kintone認定 アソシエイト/kintone認定 アプリデザインスペシャリスト/kintone認定 カスタマイズスペシャリスト チェックボックス/CHECK_BOX

Garoonのワークフローとkintoneアプリの項目対応

そして、項目の対応は次の通りです(クリックして拡大して見てみてください)。

Garoon JavaScriptカスタマイズ

このようなコードになります。リクエスト内容以外でのTipsとの違いとしては、garoon.connect.kintone.getRequestToken() がgaroon.Promiseを返却するので、kintoneのレコード登録もPromise対応させてトーンを合わせるようにしました。

/*
 * filename: customize.js
 * dependencies:
 *   global.swal (sweetalert v2.1.0) https://js.cybozu.com/sweetalert/v2.1.0/sweetalert.min.js
 */
(function () {
  'use strict';

  var kintoneAppId = 7153; // kintoneのアプリID

  // Garoonワークフローからkintoneのレコード登録する
  var createKintoneRecord = function (request, token) {
    // kintoneのレコード登録APIのリクエストボディを作成
    var body = {};
    body.app = kintoneAppId;
    body.record = {};
    body.record['取得者'] = {
      value: [{
        code: request.applicant.code
      }]
    };
    body.record['取得資格'] = {
      value: []
    };
    for (var name in request.items) {
      var item = request.items[name]
      switch (name) {
        case 'type':
          body.record['種別'] = {
            value: item.value
          };
          break;
        case 'kintone_associate':
        case 'kintone_app_design_specialist':
        case 'kintone_customization_specialist':
          if (item.value) {
            body.record['取得資格'].value.push(item.name);
          }
          break;
        default:
          break;
      }
    }
    // kintoneのCSRFトークンを付加
    body.__REQUEST_TOKEN__ = token;
    return new garoon.Promise(function (resolve, reject) {
      var xhr = new XMLHttpRequest();
      xhr.open('POST', '/k/v1/record.json');
      xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
      xhr.setRequestHeader('Content-Type', 'application/json');
      var jsonString = JSON.stringify(body);
      xhr.onload = function() {
        var res = JSON.parse(xhr.responseText);
        if (xhr.status === 200) {
          resolve(res);
          return;
        } else {
          var err = 'エラーが発生しました。';
          if('message' in res){
            err += res.message;
          }
          reject(err);
          return;
        }
      };
      xhr.onerror = function(err) {
        reject(err);
        return;
      };
      xhr.send(jsonString);
    });
  };

  // ワークフロー申請が承認されたときのイベント
  garoon.events.on('workflow.request.approve.submit.success', function (event) {
    var request = event.request;

    // kintoneのCSRFトークンを取得後レコード登録
    return garoon.connect.kintone.getRequestToken().then(function (token) {
      return createKintoneRecord(request, token);
    }).then(function(r){
      return swal('承認', '資格登録申請を承認しました。', 'success');
    }).catch(function(e){
      console.log(e);
      return swal('エラー', '資格登録申請の処理中にエラーが発生しました。', 'error');
    });
  });

})();

Garoonの設定画面で次のように設定します。

Garoonで承認してみる

スクリプトを仕込んだら、早速ワークフローを回して承認してみます。

はい、失敗しました。ステータスコード400(Bad Request)の「不正なJSON文字列です」を頂きました。POSTメソッドなので文字通りリクエストボディのJSON文字列がよろしくない事になります。

kintoneに送信されているJSON文字列を表示すると次のようになります。何となく違和感はあるのですが、・・・。

"{"app":7153,"record":{"取得者":{"value":"[{\"code\": \"r-yamashita\"}]"},"取得資格":{"value":"[\"kintone認定 アソシエイト\", \"kintone認定 アプリデザインスペシャリスト\", \"kintone認定 カスタマイズスペシャリスト\"]"},"種別":{"value":"新規"}},"__REQUEST_TOKEN__":"74beebf1-a4e7-474b-9060-56cbaab82fef"}"

同じJSONから生成されるJSON文字列をkintoneを開いている環境で見ると次のようになります。

"{"app":7153,"record":{"取得者":{"value":"[{"code": "r-yamashita"}]"},"取得資格":{"value":"["kintone認定 アソシエイト", "kintone認定 アプリデザインスペシャリスト", "kintone認定 カスタマイズスペシャリスト"]"},"種別":{"value":"新規"}},"__REQUEST_TOKEN__":"74beebf1-a4e7-474b-9060-56cbaab82fef"}"

配列の要素を括っているダブルクォーテーションに違いがあります。試しに後者の文字列をベタ貼りしてリクエストしてみると成功しました。JSON.stringify()の挙動が両者で異なり、Garoon環境下で動いているJSON.stringify()ではkintoneにうまくリクエストできないケースがあるという事になります。

GaroonのJSON.stringify()のワナ

JSON.stringify() は window.JSON.stringify() であり、ブラウザによって定義されているものですが、調べてみるとどうやらGaroon環境の window.JSON はGaroonが内部で利用しているprototype.jsというライブラリによって上書きされているようです^^;

配列部分のバックスラッシュが悪さしてる様子で、配列形式の値をとるkintoneのフィールドである「チェックボックス」、「複数選択」、「ユーザー選択」等のフィールドをリクエスト内容に入れると、失敗します。ちなみに、テキスト形式のフィールドのみのリクエストの際には成功します(Tipsのケースがこれに該当します)。

対策・回避策

こうなると、protype.jsによって奪われたwindow.JSONではkintoneは勿論その他JSONを取り扱うサービスと連携させる際の障害になるので、window.JSON相当機能を取り戻す等の何らかの方策を打つ必要があります。

こんな感じの方策が考えられます(是非拡大してご覧ください)。

https://developer.cybozu.io/hc/ja/community/posts/360000665526/comments/360000146746 より)

今回は、このうち(1)で回避する具体的な方法をご紹介したいと思います。更に、この時あげたリスクを回避し、「Polyfillがwindow.JSONの上書きをしないように・・・」を実践するものです。JSONのPolyfillを利用しつつ、カスタマイズをwebpackで閉じてあげます。

JSONのPolyfillとwebpackによるカスタマイズ

作業ディレクトリを変えて、webpackでのパッケージングを目指して準備を進めましょう。

まずJSONのpolyfillですが、MDMページでも紹介があったJSON3を利用します。また、JSON3の他にカスタマイズで利用するライブラリ(sweetalert)もnpmでインストールします。

npm i --save JSON3 sweetalert

続けて、webpackもインストールします。webpackのバージョンは執筆時点でv3.11.0でした。まずはwebpackのインストールです。

npm i --save-dev webpack

そして、webpack対応のためにメインファイルを次のように書き換えます。

/*
 * filename: customize.js
 */
(function () {
  'use strict';

  var JSON = require('json3');
  require('sweetalert');

  var kintoneAppId = 7153; // kintoneのアプリID

  // Garoonワークフローからkintoneのレコード登録する
  var createKintoneRecord = function (request, token) {
    // kintoneのレコード登録APIのリクエストボディを作成
    var body = {};
    body.app = kintoneAppId;
    body.record = {};
    body.record['取得者'] = {
      value: [{
        code: request.applicant.code
      }]
    };
    body.record['取得資格'] = {
      value: []
    };
    for (var name in request.items) {
      var item = request.items[name]
      switch (name) {
        case 'type':
          body.record['種別'] = {
            value: item.value
          };
          break;
        case 'kintone_associate':
        case 'kintone_app_design_specialist':
        case 'kintone_customization_specialist':
          if (item.value) {
            body.record['取得資格'].value.push(item.name);
          }
          break;
        default:
          break;
      }
    }
    // kintoneのCSRFトークンを付加
    body.__REQUEST_TOKEN__ = token;
    return new garoon.Promise(function (resolve, reject) {
      var xhr = new XMLHttpRequest();
      xhr.open('POST', '/k/v1/record.json');
      xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
      xhr.setRequestHeader('Content-Type', 'application/json');
      var jsonString = JSON.stringify(body);
      xhr.onload = function() {
        var res = JSON.parse(xhr.responseText);
        if (xhr.status === 200) {
          resolve(res);
          return;
        } else {
          var err = 'エラーが発生しました。';
          if('message' in res){
            err += res.message;
          }
          reject(err);
          return;
        }
      };
      xhr.onerror = function(err) {
        reject(err);
        return;
      };
      xhr.send(jsonString);
    });
  };

  // ワークフロー申請が承認されたときのイベント
  garoon.events.on('workflow.request.approve.submit.success', function (event) {
    var request = event.request;

    // kintoneのCSRFトークンを取得後レコード登録
    return garoon.connect.kintone.getRequestToken().then(function (token) {
      return createKintoneRecord(request, token);
    }).then(function(r){
      return swal('承認', '資格登録申請を承認しました。', 'success');
    }).catch(function(e){
      console.log(e);
      return swal('エラー', '資格登録申請の処理中にエラーが発生しました。', 'error');
    });
  });

})();

最後に、webpack.config.jsはこのような感じです。

module.exports = {
  entry: {
    'customize': ['./customize.js']
  },
  output: {
    path: __dirname,
    filename: '[name]_bundle.js'
  }
};

これでビルド準備ができましたので、webpackを実行します。

$(npm bin)/webpack

成功すると、customize_bundle.jsというファイルが生成されますので、これをGaroonの設定画面から次のように設定します。

Garoonで承認してみる(リベンジ)

ワークフローの承認ボタンを押すと、今度は無事成功しました。

kintoneの方を見にいってみると、無事レコードが登録されていました。

まとめ・所感

Garoonのワークフロー承認に合わせて、kintoneのレコード登録する方法を見てきました。

「Garoonで〇〇したら、kintoneで××したい」のニーズは以前からあったものの、これまではバッチプログラムでの対応が必要でした。しかし、Garoon JavaScript APIのイベントの登場で敷居がグッと下がったと思います。

また、kintoneの複数選択系のフィールドを含むレコードの登録・更新には注意が必要なことがわかりました。ただ、これは・・・原因に気づきづらい上に回避策もやや込み入ってるので、「(4)公式対応を待つ」で解消されることを祈りたいと思います。

 


株式会社ジョイゾー