kintone ポータルに Google Workspace 新着メール一覧とスケジュール情報を表示しよう

目次

caution
警告

Moment.js はメンテナンスモードになり、 日付処理できる代替ライブラリへの移行 (External link) が推奨されています。
代替ライブラリのひとつ Luxon (External link) については、 kintone カスタマイズでの導入方法の紹介記事 があります。

はじめに

この記事では、Google Workspace に紐づく Google アカウントの直近のスケジュール情報や Gmail で受信した新着メールの一覧を表示するポータルカスタマイズを紹介します。

Gmail や Google カレンダーのページを開いて確認するところを、kintone を開くだけで最新情報を確認できて便利なカスタマイズです。

必要なもの

完成イメージ

直近のスケジュール表示

  • ポータルの左側に、直近のスケジュールをカレンダーごとに最大 10 件表示します。 *1
  • カレンダーの切り替えボタンで、複数のカレンダーを切り替え表示できます。
  • スケジュール一覧をクリックすると、Google カレンダーのページに遷移します。 *2

新着メール一覧表示

  • ポータルの右側に、受信トレイから取得した直近の新着メールを 5 件表示します。
  • 未読メールは太字で表示されます。
  • メール一覧をクリックすると、Gmail でクリックしたメールの詳細画面を表示します *2

*1 スケジュール表示のみをサポートしています。
スケジュールを修正したい場合は、スケジュール一覧をクリックし、表示される Google カレンダーの画面で編集してください。 ^

*2 Google Workspace の仕様上、複数アカウントにログインしている場合、一番上のアカウントが使用されます。
kintone ポータルにはポータル表示時にログインしたアカウントの Gmail やスケジュール情報を表示しますが、一覧をクリックしたときの遷移先のページは、一番上のアカウントとして開かれます。
Gmail の場合は、メールの詳細画面ではなくそのアカウントの受信トレイを表示します。 ^

システム構成

  • kintone ポータルから Google API を実行し、Gmail や Google カレンダーの情報を取得します。

注意事項

  • タイトル部分のアイコン(メールやカレンダー)は、kintone 製品のアイコンを使用しています。
    製品のアップデートにより、通知アイコンが正常に表示されなくなる場合があります。

Google Cloud Platform の設定

Google API を利用できるよう、Google Cloud Platform でプロジェクトを作成します。

  1. Google API Console (External link) に Google Workspace 管理者アカウトでログインします。

  2. [プロジェクトの選択]をクリックします。

  3. [新しいプロジェクト]をクリックします。

  4. プロジェクト名を入力し、[作成]ボタンをクリックします。この記事では「kintone-portal」としています。
    プロジェクトが作成されると、作成したプロジェクトのダッシュボードに自動で移動します。

  5. [API とサービスを有効化]をクリックします。

  6. 「API とサービスを検索」ボックスに「Gmail」と入力します。「Gmail API」が表示されるので選択します。

  7. [有効にする]ボタンをクリックします。サービスが有効化されると、Gmail API の概要ページに自動で遷移します。

  8. [認証情報を作成]ボタンをクリックします。

  9. 次の内容を入力します。入力が終わったら、[必要な認証情報]ボタンをクリックします。

    項目
    使用する API 「Gmail API」を選択します。
    API を呼び出す場所 「Webブラウザー(JavaScript)」を選択します。
    アクセスするデータの種類 「ユーザー データ」を選択します。
  10. 「OAuth 同意画面の表示」モーダルが表示されます。[同意画面を設定]をクリックします。

  11. 次の内容を入力します。入力が終わったら、[保存]ボタンをクリックします。


    必要最低限の設定項目を記載しています。その他の設定項目は、任意に設定してください。

    項目
    アプリケーション名 任意の値を入力します。この記事では「kintone-portal」としています。
    サポートメール メールアドレスを選択します。
    承認済みドメイン 「cybozu.com」を入力します。
  12. 認証情報ページに自動で遷移します。[認証情報を作成]ボタンをクリックし、「OAuth クライアント ID」を選択します。

  13. 次の内容を入力します。入力が終わったら、[作成]ボタンをクリックします。

    項目
    アプリケーションの種類 「Webアプリケーション」を選択します。
    名前 任意の値を入力します。この記事では「kintone-portal」としています。
    承認済みの JavaScript 生成元 「https://SUBDOMAIN.cybozu.com」を入力します。
    SUBDOMAIN はご利用の kintone 環境に合わせて変更してください。
  14. OAuth クライアント ID が生成され、クライアント ID が表示されます。
    クライアント ID は kintone に適用するカスタマイズファイルに利用します。メモして控えておいてください。
    [完了]ボタンをクリックします。


    クライアント ID は、「API とサービス」で作成プロジェクトを選択し、左ペインから「認証情報」を選択すると、再確認できます。

  15. 同様の手順で、Google Calendar API も有効化します。左ペインから「ダッシュボード」を選択します。[API とサービスを有効化]をクリックします。

  16. 「API とサービスを検索」ボックスに「Calendar」と入力します。「Google Calendar API」が表示されるので選択します。

  17. [有効にする]ボタンをクリックします。

  18. [認証情報を作成]ボタンをクリックします。

  19. 次の内容を入力します。入力が終わったら、[必要な認証情報]ボタンをクリックします。

    項目
    使用する API 「Google Calendar API」を選択します。
    API を呼び出す場所 「Webブラウザー(JavaScript)」を選択します。
    アクセスするデータの種類 「ユーザー データ」を選択します。
  20. 認証情報は、Gmail API の有効化で作成したものと同じ認証情報を使用します。[完了]ボタンをクリックします。

Google Cloud Platform の設定は以上です。

kintone の設定

このカスタマイズでは、次の外部ライブラリを利用しています。

  • jQuery v3.3.1, ドキュメント (External link)
    • https://js.cybozu.com/jquery/3.3.1/jquery.min.js
  • Google JavaScript クライアントライブラリ ドキュメント (External link)
    Google API を扱うためのライブラリです。
    • https://apis.google.com/js/client.js
    • https://apis.google.com/js/platform.js
  • Moment.js v2.24.0, ドキュメント (External link)
    • https://js.cybozu.com/momentjs/2.24.0/moment.min.js
  • Loaders.css v0.1.2, ドキュメント (External link)
    • loaders.css.js
    • loaders.min.css

ポータルカスタマイズをするには、kintone 全体にカスタマイズを適用します。
手順の詳細は「 JavaScriptやCSSを使用したカスタマイズ (External link) 」を参照してください。

  1. システム管理画面を開きます。
  2. [JavaScript や CSS で kintone 全体をカスタマイズする]をクリックします。
  3. 次の内容を入力します。入力が終わったら、[保存]ボタンをクリックします。

    項目
    適用範囲 ポータルを表示させたいユーザーの範囲を選択します。
    PC用のJavaScriptファイル 次の順で指定します。
    • https://js.cybozu.com/jquery/3.1.1/jquery.min.js
    • https://apis.google.com/js/client.js
    • https://apis.google.com/js/platform.js
    • https://js.cybozu.com/momentjs/2.24.0/moment.min.js
    • loaders.css.js
    • カスタマイズファイル(kintone-portal-gsuite.js)
      詳細は、後述の サンプルコード(kintone-portal-gsuite.js) を参照してください。
    PC用のCSSファイル 次の順で指定します。

loaders.css の入手方法

  1. https://github.com/ConnorAtherton/loaders.css/releases/tag/0.1.2 (External link) にアクセスします。
  2. Assets の「Source code(zip)」をクリックし、Zip ファイルをダウンロードします。
  3. ダウンロードした Zip ファイルを解凍します。解凍したフォルダーの以下のファイルを利用します。
    • loaders.css.js
    • loaders.min.css

kintone の設定は以上です。

サンプルコード

kintone-portal-gsuite.js

12 行目 GOOGLE_CLIENT_ID の値: Google Cloud Platform の設定 でメモしておいたクライアント ID に置き換えてください。

  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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
/*
 * kintone x Gsuite Portal Sample program
 * Copyright (c) 2019 Cybozu
 *
 * Licensed under the MIT License
*/

(($) => {
  'use strict';

  // Google API 設定で発行したクライアント ID
  const GOOGLE_CLIENT_ID = 'GOOGLE_API_CLIENT_ID';

  // kintone ポータルに表示する要素を作る
  const createElement = () => {
    let space = null;
    const $portal = $('<div class="main-content">');
    $portal.append(
      '<div class="left-content">'
      + '  <div class="left-container">'
      + '    <div class="custom-container-header-calender"><h3>カレンダー</h3></div>'
      + '    <div class="custom-container-body-calender">'
      + '      <div id="calendars"></div>'
      + '      <div id="events"></div>'
      + '    </div>'
      + '  </div>'
      + '</div>');
    $portal.append(
      '<div class="right-content">'
      + '  <div class="right-container">'
      + '    <div class="custom-container-header-mail"><h3>メール</h3></div>'
      + '    <div class="custom-container-body-mail">'
      + '      <div class="loader-inner ball-grid-pulse"></div>'
      + '    </div>'
      + '  </div>'
      + '</div>');
    space = kintone.portal.getContentSpaceElement();
    $(space).append($portal);
  };

  // Google API を利用するために認証する
  const getAuth = () => {
    return new kintone.Promise((resolve, reject) => {
      $('.loader-inner').loaders();
      gapi.load('auth2', () => {
        gapi.auth2.init({
          client_id: GOOGLE_CLIENT_ID,
          fetch_basic_profile: true,
          scope: 'https://www.googleapis.com/auth/gmail.readonly '
              + 'https://www.googleapis.com/auth/calendar.readonly '
              + 'https://www.googleapis.com/auth/calendar.events.readonly '
              + 'https://www.googleapis.com/auth/calendar.settings.readonly'
        }).then(() => {
          if (gapi.auth2.getAuthInstance().isSignedIn.get() === true) {
            resolve();
          } else {
            gapi.auth2.getAuthInstance().signIn().then(resolve);
          }
        });
      });
    });
  };

  // Google Calendar からカレンダー一覧を取得する
  const getCalendar = () => {
    moment.locale('ja', {
      weekdaysShort: ['日', '月', '火', '水', '木', '金', '土']
    });

    gapi.client.load('calendar', 'v3', () => {
      gapi.client.calendar.calendarList.list().execute((resp) => {
        resp.items.forEach((calendar) => {
          $('#calendars').append(
            $('<div>')
              .addClass('calendar-item')
              .attr('data-calendar-id', calendar.id)
              .css('color', calendar.backgroundColor)
              .css('border', '1px solid ' + calendar.backgroundColor)
              .on('click', handleCalendarClick)
              .text(calendar.summary));
        });
        getEvents(resp.items[0].id);
      });
    });
  };

  // ポータルのカレンダー一覧をクリックした時のイベントを作成する
  const handleCalendarClick = (event) => {
    const calendarId = $(event.target).attr('data-calendar-id');
    getEvents(calendarId);
  };

  // カレンダーID からカレンダーの情報(最大10件)を取得する
  const getEvents = (calendarId) => {
    gapi.client.load('calendar', 'v3', () => {
      gapi.client.calendar.events.list({
        calendarId: calendarId,
        timeMin: (new Date()).toISOString(),
        showDeleted: false,
        singleEvents: true,
        maxResults: 10,
        orderBy: 'startTime'
      }).execute((resp) => {
        let date = '';
        $('#events').html('');
        resp.items.forEach((event) => {
          let dateLabel = '';
          let startDate = '';

          if (event.start.dateTime) {
            startDate = moment(event.start.dateTime).format('YYYY/MM/DD');
            dateLabel = moment(event.start.dateTime).format('HH:mm') + ' - ' + moment(event.end.dateTime).format('HH:mm');
          } else if (event.start.date) {
            startDate = event.start.date;
            dateLabel = '[終日]';
          }

          if (date !== startDate) {
            $('#events').append(
              $('<div>')
                .addClass('title')
                .text(moment(startDate).format('YYYY/MM/DD(ddd)')));
            date = moment(startDate).format('YYYY/MM/DD');
          }

          $('#events').append(
            $('<a>')
              .addClass('event')
              .attr('href', event.htmlLink)
              .attr('target', '_blank')
              .text(dateLabel + ' ' + event.summary));
        });
      });
    });
  };

  // Gmail の受信トレイから新着メール5件を取得する
  const getMail = () => {
    gapi.client.load('gmail', 'v1', () => {
      const option = {
        includeSpamTrash: false,
        userId: 'me',
        labelIds: ['INBOX'],
        maxResults: 5
      };
      const request = gapi.client.gmail.users.messages.list(option);
      request.execute((resp) => {
        const requests = {};
        const httpBatch = gapi.client.newHttpBatch();
        $.each(resp.messages, (i, message) => {
          requests[message.id] = gapi.client.request({
            path: 'gmail/v1/users/me/messages/' + message.id,
            method: 'GET',
            params: {format: 'full'}
          });
          httpBatch.add(requests[message.id], {id: message.id});
        });
        httpBatch.execute((response) => {
          const $table = $('<table>');
          const $tbody = $('<tbody>');
          $table.append(
            '<thead>'
            + '  <tr>'
            + '    <th>日時</th>'
            + '    <th>From</th>'
            + '    <th>件名</th>'
            + '  </tr>'
            + '</thead>'
          );

          $.each(response, (i, mail) => {
            const $tr = $(
              '<tr class="tr-row" link="' + mail.id + '">'
              + '  <td class="td-date"></td>'
              + '  <td class="td-from"></td>'
              + '  <td class="td-title"></td>'
              + '</tr>'
            );
            $.each(mail.result.payload.headers, (index, header) => {
              if (header.name === 'Date') $tr.children().eq(0).text(moment(header.value).format('YYYY/MM/DD HH:mm'));
              if (header.name === 'From') $tr.children().eq(1).text(header.value);
              if (header.name === 'Subject') $tr.children().eq(2).html(header.value);
            });
            if (mail.result.labelIds.indexOf('UNREAD') !== -1) {
              $tr.css('font-weight', 'bold');
            }
            $tbody.append($tr);
          });
          $table.append($tbody);
          $('.loader-inner').css('display', 'none');
          $('.custom-container-body-mail').append($table);
          $('.tr-row').on('click', (event) => {
            window.open('https://mail.google.com/mail/u/0/#inbox/' + $(event.currentTarget).attr('link'));
          });
        });
      });
    });
  };

  // kintone ポータルにスケジュール一覧とメール一覧を表示する
  kintone.events.on('portal.show', (event) => {
    createElement();
    getAuth().then((resp) => {
      getMail(resp);
      getCalendar(resp);
    });
  });
})(jQuery);

kintone-portal-gsuite.css

  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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
/*
 * kintone x Gsuite Portal Sample css
 * Copyright (c) 2019 Cybozu
 *
 * Licensed under the MIT License
*/

.main-content {
  width: 100%;
  height: auto;
}

.left-content {
  width: 50%;
  float: left;
}

.right-content {
  width: 50%;
  float: left;
}

.left-container {
  width: auto;
  height: auto;
  background-color: #ffffff;
  margin: 16px 8px 16px 16px;
  letter-spacing: .04em;
}

.right-container {
  width: auto;
  height: auto;
  background-color: #ffffff;
  margin: 16px 16px 16px 8px;
  letter-spacing: .04em;
}

.custom-container-header-calender {
  position: relative;
  background-image: url(https://static.cybozu.com/contents/k/image/icon/app/calendar.png);
  background-position: left top;
  background-repeat: no-repeat;
  height: 56px;
  line-height: 56px;
  font-size: 20px;
  padding: 0 72px 0 80px;
  border-bottom: 1px solid #e3e7e8;
}

.custom-container-header-mail {
  background-image: url(https://static.cybozu.com/contents/k/image/icon/app/mail.png);
  background-position: left top;
  background-repeat: no-repeat;
  height: 56px;
  line-height: 56px;
  font-size: 20px;
  padding: 0 72px 0 80px;
  border-bottom: 1px solid #e3e7e8;
}

.custom-container-body-calender {
  text-align: center;
  height: 300px;
  /* line-height: 300px; */
  font-size: 20px;
  background-color: #f3f3f3;
  overflow-y: scroll;
}

.custom-container-body-mail {
  height: 300px;
  text-align: center;
  font-size: 12px;
}

.custom-container-body-mail table{
  width: 100%;
  height: 300px;
}

.custom-container-body-mail table thead tr {
  height: 40px;
  background-color: #f7f9fa;
  border-bottom: 1px solid #e3e7e8;
}

.custom-container-body-mail table tbody tr {
  height: 52px;
  cursor: pointer;
}

.custom-container-body-mail table tbody tr:nth-child(even) {
  background-color: #f2f9fe;
}

.td-date {
  width: 20%;
  padding: 0 5px;
  overflow: hidden;
  text-overflow: ellipsis;
}

.td-from {
  width: 30%;
  padding: 0 5px;
  overflow: hidden;
  text-overflow: ellipsis;
}

.td-title {
  width: 50%;
  padding: 0 5px;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ball-grid-pulse {
  padding: 50px;
  padding: 100px 0 0 46%;
}

.ball-grid-pulse > div {
  background-color: #e3e7e8;
}

#calendars {
  border-bottom: 1px solid #dddddd;
  background-color: #ffffff;
  padding: 6px;
  text-align: left;
  line-height: initial;
  height: initial;
}

#calendars .calendar-item {
  display: inline-block;
  background-color: #ffffff;
  padding: 6px;
  font-size: 12px;
  font-weight: 100;
  border-radius: 3px;
  margin: 6px;
  line-height: initial;
  height: initial;
  cursor: pointer;
  transition: 0.2s;
}

#calendars .calendar-item:hover {
  opacity: 0.5;
}

#events .title {
  display: block;
  border-bottom: 1px solid #dddddd;
  line-height: initial;
  height: initial;
  padding: 4px 12px;
  text-align: left;
  font-size: 12px;
  background-color: #f0f0f0;
  text-decoration: none;
  color: #333333;
  font-weight: 700;
}

#events .event {
  display: block;
  border-bottom: 1px solid #dddddd;
  line-height: initial;
  height: initial;
  padding: 8px 12px;
  text-align: left;
  cursor: pointer;
  font-size: 14px;
  background-color: #ffffff;
  text-decoration: none;
  color: #666666;
  transition: 0.2s;
}

#events .event:hover {
  background-color: #f5f5f5;
}

動作確認

  1. kintone にログインし、ポータル画面を開きます。
  2. Google アカウントへのログインを求めるポップアップが表示されたら、Google Workspace で利用しているアカウントでログインします。
    有効なセッションがある場合は、ログインは要求されずにカレンダー一覧や新着メール一覧が表示されます。
  3. カスタマイズで追加したカレンダー一覧や新着メール一覧がポータルに表示されます。

初回のみ、Google アカウントへのログイン後に権限付与を求めるポップアップが表示されます。
次の手順で、許可します。

  1. 「このアプリは確認されていません」ポップアップが表示されます。「詳細」リンクをクリックします。

  2. 「cybozu.com(安全ではないページ)に移動」リンクをクリックします。

  3. 権限の付与を確認するモーダルが表示されます。以下のすべての権限に対し[許可]ボタンをクリックします。

    • メール メッセージと設定の表示
    • カレンダーの設定を表示します。
    • すべてのカレンダーの予定を表示
    • カレンダーを表示
  4. 「選択内容を確認してください」ポップアップで、[許可]ボタンをクリックします。クリックすると、kintone ポータルにスケジュールとメール一覧が表示されます。

おわりに

この記事では、Google Workspace という外部サービスのデータを kintone ポータルに表示するカスタマイズを紹介しました。

他にも Chrome 拡張の Kintone Portal Designer を使ってお手軽にポータルをカスタマイズする kintone ポータルカスタマイズ例 もあるので、ぜひ参照してください。

使用している API

information

この Tips は、2019 年 11 月版 kintone で動作を確認しています。