Garoon カスタマイズで外部システムの API を実行する〜打合せコスト算出カスタマイズ〜

目次

はじめに

Garoon スケジュール・ポータルのカスタマイズをはじめよう〜会議効率化カスタマイズ〜 では、Garoon JavaScript API と Garoon REST API の使い方を知り、Garoon カスタマイズの基本を学びました。
この記事では、Garoon JavaScript カスタマイズで外部システムの API を実行する方法を学んでいきましょう。

Garoon と社内で利用している他のシステムを連携させると、Garoon のポータル上に他のシステムの情報を出力したり、Garoon のワークフローを承認したら自動で外部システムにそのデータを送信したりできます。

この記事の内容は、「Cybozu Days 2019 の Garoon 中級ハンズオン〜API を使って作る、カスタマイズポータル作成ハンズオン〜」で紹介したものです。

この記事で学べること

  • Garoon JavaScript API を使って、外部システムの API を実行する方法

環境準備

  • Garoon クラウド版
  • テキストエディター

注意事項

次の画面ではカスタマイズを利用できません。

  • モバイル表示
  • モバイル用アプリ(KUNAI および Garoon モバイル)

ハンズオン

完成イメージ

参加者ごとに設定された単価から算出された予定の打合せコストを、予定の詳細画面で見える化します。
算出対象は「予定メニュー」が「打合」の予定です。

算出コストの内訳は、ブラウザーの開発者コンソールに出力されます。

参加者の単価は、ユーザー情報のカスタマイズ項目に設定します。
設定していないユーザーの単価は、0 円で算出されます。

カスタマイズの適用範囲を人事部などのユーザーに限定して設定すると、適用されていないユーザーに打合せコストが表示されません。
開発者コンソールの算出コストの明細も同様です。
また、ユーザー情報のカスタマイズ項目も非公開やユーザーによる変更を不可に設定すると、cybozu.com 管理者以外のユーザーには、プロフィール画面に単価は表示されません。

Step 1: ユーザー項目の追加

この設定には、cybozu.com 共通管理者の権限が必要です。
手順の詳細は「 ユーザー情報の項目を追加する(カスタマイズ項目) (External link) 」を参照してください。

  1. Garoon メニュー右の歯車アイコンをクリックし、[cybozu.com 共通管理]を選択します。
  2. 「ユーザー管理」の[組織/ユーザー]を選択します。
  3. [プロフィール項目の設定]をクリックします。
  4. 「カスタマイズ項目の設定」の[追加]ボタンをクリックします。
  5. 以下の内容を入力します。入力が終わったら、[保存]ボタンをクリックします。
    項目 設定する値
    項目名 任意の値を入力します。
    この記事では「単価」とします。
    項目コード 「UnitPrice」を入力します。
    タイプ 「文字列(1行)」を選択します。
    公開/非公開 「公開する」のチェックを外します。
    ユーザーによる変更 「ユーザーによる変更を許可する」のチェックを外します。

Step 2: プロキシ API の設定

  1. Garoon メニュー右の歯車アイコンをクリックし、[Garoon システム管理]を選択します。
  2. [基本システムの管理]タブを選択し、[API]を選択します。
  3. [プロキシ API の設定]を選択します。
  4. [追加する]をクリックします。
  5. 以下の内容を入力します。入力が終わったら、[追加する]ボタンをクリックします。

    項目 設定する値
    ステータス 「有効」を選択します。
    プロキシコード 「CalculatingCosts」を入力します。
    メソッド 「GET」を選択します。
    URL https://{subdomain}.cybozu.com
    {subdomain} は利用している Garoon 環境のサブドメインに置き換えてください。
    ヘッダー 次のキーと値の組み合わせを設定します。

Step 3: カスタマイズファイルの作成

  1. テキストエディタを開いて、次のコードの内容をコピー&ペーストします。

      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    
    /**
     * Garoon meeting efficiency customize(portlet)
     * Copyright (c) 2020 Cybozu
     *
     * Licensed under the MIT License
     * https://opensource.org/license/mit/
     */
    
    (function($) {
      'use strict';
    
      // 「時間単価」が登録されている、ユーザー情報のカスタマイズ項目名
      const userCustom = 'UnitPrice';
      // プロキシコード
      const proxyCode = 'CalculatingCosts';
      // コスト算出対象の予定メニュー
      const MEETING = '打合';
    
      /**
       * 全参加者の「時間単価」(カスタマイズ項目)に数値が入っているかを確認する関数
       * @param {array} users 打合参加者
       * @returns {boolean} true:全て数値 false:1つでも数値以外の値が入っている
       */
      const isCheckedUnitPrice = function(users) {
        return users.every((user) => {
          const customItems = user.customItemValues;
          if (!customItems.length) {
            return true;
          }
          const unitPrice = customItems.filter((item) => {
            return item.code === userCustom;
          })[0];
          if (!unitPrice.value || unitPrice.value === '0') {
            return true;
          }
          return Number(parseInt(unitPrice.value, 10));
        });
      };
    
      /**
       * 打合コストを求める関数
       * @param {array} users 打合参加者
       * @param {number} meetingTime 打合所要時間
       * @returns {number} 打合コスト(人件費)
       */
      const calculateLaborCost = function(users, meetingTime) {
        const cost = users.reduce((price, user) => {
          const customItems = user.customItemValues;
          const unitPrice = customItems.filter((item) => {
            return item.code === userCustom;
          })[0];
          if (!unitPrice) {
            return price;
          }
          return price + Number(unitPrice.value);
        }, 0);
    
        const total = Math.round(cost * parseFloat(meetingTime));
        // 会議のコストを表示
        console.log('打合コストの内訳: ' + cost.toLocaleString() + ' 円 * ' + meetingTime.toLocaleString() + '時間 = ' + total.toLocaleString() + ' 円');
        return total;
      };
    
      garoon.events.on('schedule.event.detail.show', (event) => {
        const scheduleEvent = event.event;
        let query = '';
        const start = scheduleEvent.start.dateTime;
        const end = scheduleEvent.end.dateTime;
        const attendees = scheduleEvent.attendees;
        const startTime = new Date(start);
        const endTime = new Date(end);
        const meetingTime = (endTime - startTime) / (1000 * 60 * 60);
        const path = 'https://' + location.hostname + '/v1/users.json?';
    
        // 予定メニューが「打合」以外、もしくは参加者が0名の場合、処理を終了する
        if (scheduleEvent.eventMenu !== MEETING || attendees < 1) {
          return;
        }
    
        // code(ログイン名)を利用したクエリ生成
        query = attendees.map((attendee, index) => {
          return 'codes[' + index + ']=' + attendee.code;
        }).join('&');
    
        garoon.base.proxy.send(proxyCode, path + query, 'GET', {}, {}).then((resp) => {
          const users = JSON.parse(resp[0]).users;
    
          if (!isCheckedUnitPrice(users)) {
            window.alert('数値ではない値が時間単価に設定されている可能性があります');
            return;
          }
    
          const total = calculateLaborCost(users, meetingTime);
          const headerSpace = garoon.schedule.event.getHeaderSpaceElement();
          const $span = $('<STRONG>', {
            id: 'items',
            class: 'sat_color1_grn_kit',
            text: '※本打合コスト: ' + total.toLocaleString() + ' 円'
          });
          $span.prependTo(headerSpace);
        }).catch((e) => {
          window.alert('コスト表示に失敗しました。原因については、システム管理者にご相談ください。');
          console.log('※エラー詳細 \n ', e);
        });
      });
    })(jQuery.noConflict(true));
  2. ファイルの拡張子「.js」、文字コードは「UTF-8(BOM なし)」で、ファイルに名前を付けて保存します。
    この記事では、ファイル名を calculating-costs.js としています。

Step 4: カスタマイズの適用

カスタマイズグループを作成し、スケジュールのカスタマイズファイルを適用します。
手順の詳細は「 スケジュールのカスタマイズ設定 (External link) 」を参照してください。

  1. Garoon メニュー右の歯車アイコンをクリックし、[Garoon システム管理]を選択します。
  2. [各アプリケーションの管理]タブを選択し、[スケジュール]を選択します。
  3. [JavaScript/CSS によるカスタマイズ]を選択します。
  4. [カスタマイズグループを追加する]をクリックします。
  5. 次の内容を入力します。入力が終わったら、[追加する]ボタンをクリックします。
    項目 設定する値
    カスタマイズ 「適用する」を選択します。
    カスタマイズグループ名 任意の値を入力します。この記事では「会議効率化カスタマイズ」としています。
    適用対象 管理部など、打合せのコストを表示しても問題ないユーザーや部署を選択してください。
    JavaScript カスタマイズ 以下の順で、URL およびファイルを指定します。
    1. https://js.cybozu.com/jquery/3.1.1/jquery.min.js
    2. calculating-costs.js(カスタマイズファイル)
    CSS カスタマイズ grn_kit.css を指定します
    grn_kit.css(Garoon html/css/image-Kit for Customize)の入手方法

grn_kit.css(Garoon html/css/image-Kit for Customize)の入手方法

  1. https://github.com/garoon/css-for-customize (External link) にアクセスします。
  2. [Clone or download]ボタンをクリックして、「Download ZIP」を選択します。
  3. ダウンロードした zip ファイルを解凍します。
  4. 解凍したファイルの「css」フォルダー以下の「grn_kit.css」を利用します。

コードの解説

プロキシ API 設定

プロキシ API 設定は、ブラウザーから外部システムの API を実行するための Garoon のしくみです。
garoon.base.proxy.send() を実行すると、あらかじめ設定しておいたプロキシ設定を利用して、Garoon サーバーから外部システムの API へのリクエストを送信します。
参考: プロキシ API の設定 (External link)

また、外部システムの API の実行に必要な認証情報を、プロキシ API 設定で設定しカスタマイズファイル内にハードコーディングしないので、セキュアに API を実行できます。
参考: セキュアコーディングガイドライン - 認証情報や認可情報を適切に取り扱う
User API の場合は、「X-Cybozu-Authorization」というリクエストヘッダーが認証情報です。

tips
補足

今回のサンプルでは、cybozu.com の User API を外部システムの API に見立て実行していますが、Garoon カスタマイズで User API を実行する場合には、プロキシ API は設定不要で API を実行できます。
例: User API のユーザー情報を使って Garoon ワークフローをカスタマイズする

garoon.base.proxy.send()

garoon.base.proxy.send() は、Garoon から外部システムの API を実行する Garoon JavaScript API です。
今回のコードでは、次の箇所で利用しています。

 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
    garoon.base.proxy.send(proxyCode, path + query, 'GET', {}, {}).then((resp) => {
      const users = JSON.parse(resp[0]).users;

      if (!isCheckedUnitPrice(users)) {
        window.alert('数値ではない値が時間単価に設定されている可能性があります');
        return;
      }

      const total = calculateLaborCost(users, meetingTime);
      const headerSpace = garoon.schedule.event.getHeaderSpaceElement();
      const $span = $('<STRONG>', {
        id: 'items',
        class: 'sat_color1_grn_kit',
        text: '※本打合コスト: ' + total.toLocaleString() + ' 円'
      });
      $span.prependTo(headerSpace);
    }).catch((e) => {
      window.alert('コスト表示に失敗しました。原因については、システム管理者にご相談ください。');
      console.log('※エラー詳細 \n ', e);
    });

今回実行する ユーザー情報をエクスポートする API に合わせて、garoon.base.proxy.send(プロキシコード, URL, メソッド, APIに渡すデータ) という形で指定します。
このとき、Promise を使って API を実行しています。
Promise については、 Promise と async/await kintone カスタマイズで非同期処理をする を参照してください。

  • プロキシコード:「プロキシ API 設定」で設定したプロキシコード

  • URL:API ユーザーエクスポート API(JSON)の URL を指定します。検索条件(今回は予定の参加者のユーザーコード)を URL パラメーターで指定しています。
    今回のコードでは、変数 pathquery を結合した値を指定していますが、pathquery は次の部分で定義しています。

    73
    
        const path = 'https://' + location.hostname + '/v1/users.json?';
    80
    81
    82
    83
    
        // code(ログイン名)を利用したクエリ生成
        query = attendees.map((attendee, index) => {
          return 'codes[' + index + ']=' + attendee.code;
        }).join('&');

    たとえば、予定の参加者のユーザーコードが yamada と sato の場合、次の URL が指定されます。
    https://{subdomain}.cybozu.com/v1/users.json?codes[0]=yamada&codes[1]=sato

  • メソッド:GET

  • API に渡すデータ:ありません。

正常に実行できたら .then() の引数で指定した関数の処理を実行し、エラーが発生したら .catch() の引数で指定した関数の処理を実行します。
今回、正常に実行できたときは、取得した UnitPrice の値を使って打合せコストを算出し、画面上に表示しています。

動作確認

  1. ユーザー情報を変更し、「UnitPrice」に単価を設定します。
    参考: ユーザー情報の変更 (External link)
  2. 予定を登録します。
    予定メニュー:「打合」を選択します
    参加者:1. で単価を設定したユーザーを選択します。
  3. 登録した予定の詳細画面を開きます。タイトルの下部に打合せコストが表示されていることを確認します。

おわりに

cybozu developer network では、さまざまな Garoon の連携カスタマイズ Tips を公開しています。
Tips に掲載しているコードで「どんなことをしているか?」を確認しながら、ぜひカスタマイズに挑戦してみてください。

利用している Garoon API

利用しているライブラリ

  • jQuery v3.1.1, ドキュメント(外部サイト) (External link)
    HTML 要素の生成や操作を楽に扱うことができるライブラリです。
    打合せコストを表示する <>HTML 要素の作成や予定の詳細画面への挿入などに利用しています。
  • Garoon html/css/image-Kit for Customize, ドキュメント
    ボタンなどの UI パーツを Garoon の見た目に調和させるスタイルシートのライブラリです。
    打合せコストを表示する HTML 要素の背景色に利用しています。
information

この Tips は、2020 年 6 月版 Garoon で動作を確認しています。