LoginSignup
3
1

cli-kintoneでCloudStorageからkintoneアプリにリストア・おまけのBIツール(LookerStudio)連携 (2/2)

Last updated at Posted at 2022-12-24

この記事は、kintoneアドベントカレンダー2022の記事です。

はじめに

前編ではカーネマン社の金子さんにcli-kintoneを利用してkintoneアプリをCloudStorageとcloudsqlに自動バックアップする関数を紹介してもらいました。

後編では、そのバックアップしたデータをkintoneに戻す関数の実装を紹介しています。
おまけ編では、Google製の無償BIツールLooker Studioを利用したBIツール連携に挑戦してみます。

Cloud Storageからkintoneへデータを復帰させる

構成図

試験実装のため、関数をキックするのはユーザーの操作からではなくTerminalからHTTP経由で行います。
そのため現状では処理の起点はCloud Functionsからということになります。
※本来はバックアップ一覧からリストアするファイルをユーザーに選択させるべきですが、今回はCloud StorageにバックアップしたCSVファイルをリストアできることを確認することが最優先だったため、最新のバックアップを戻す実装にしています。
clikinton構成図.003.jpeg

プロジェクト構成

プロジェクト構成は先に紹介した clikinton-py-gcp と同じです。

import-to-kintone
├── README.md
├── .env.yaml // 環境変数
├── cli-kintone-linux
│   ├── LICENSE
│   ├── NOTICE
│   └── cli-kintone
├── deploy
│   ├── cli-kintone
│   ├── main.py
│   └── requirements.txt
├── deploy.sh
├── main.py
└── requirements.txt

コード

このコードでは、Cloud Storageに保存されたCSVファイルをcli-kintoneを使ってkintoneアプリにリストアします。本来でしたらkintoneプラグインを作って、Cloud Storageに保存されているバックアップのリストを表示して、ユーザーにリストアさせるバージョンを指定させるような画面を作ると便利なのですが、今回は一時的な仕様として「バックアップファイルのうち最新のものをリストアする」実装になっています。
また、cli-kintoneでデータをリストアさせる際、ソースファイルはローカルのパスで指定しなければならないため、Cloud Storage内のファイルを読み出してから一時ファイル tmp.csv を作成してそれを指定しています。この一時ファイルは毎回新しいデータで内容を上書きしているため、削除はしません。

main.py
import io

import functions_framework
import os
import subprocess
from google.cloud import storage

@functions_framework.http
def import_to_kintone(request):
    # 環境変数を読み込む
    domain = os.environ.get('DOMAIN')
    app_id = os.environ.get('APPID')
    token = os.environ.get('TOKEN')
    bucket_name = os.environ.get('BUCKET')

    storage_client = storage.Client()
    file_list = list_backups(storage_client, bucket_name)

    bucket_name = storage_client.get_bucket(bucket_name)
    # TODO ユーザーがkintoneプラグインでバックアップファイルを選択できるようになるまでは最新バックアップを自動的に指定する
    file_name = file_list[-1]
    blob = bucket_name.blob(file_name)
    buffer = blob.download_as_string()
    # ファイルを削除すること
    with open('./tmp.csv', 'w', encoding='utf-8') as f:
        f.write(buffer.decode('utf-8'))

    args = f"record import --base-url https://{domain}.cybozu.com --app {app_id} --api-token {token}" \
           f" --file-path {'./tmp.csv'}"
    res = subprocess.run([f"./cli-kintone {args}"], shell=True, encoding='utf-8', capture_output=True, text=True)

    return 'success'


# 指定バケット内のファイルリストを取得
def list_backups(storage_client, bucket_name):
    files = storage_client.list_blobs(bucket_name)
    file_list = []
    for file in files:
        file_list.append(file.name)
    return file_list

Cloud Storageに保存されていたCSVデータがkintoneにリストアされました。
レコード番号の重複を避けるため、CSVデータがCloud Storageに保存される前の処理でレコード番号カラムは削除されています。下記スクリーンショットのレコード番号は、データのリストア時に新たに採番されたものです。
スクリーンショット 2022-12-18 10.38.52.png

まとめ

上記の試験実装では、バックアップしたCSVファイルから元のkintoneアプリにリストアすることができました。このように、 cli-kintoneを使用すると簡単にバックアップが取れる他、その作業をクラウドサービス上に構築してしまえば、バックアップ作業を自動化できることがわかりました。

BIツール連携(おまけ)

Cloud StorageにkintoneデータをCSVファイルとして保存できれば、これをデータソースとしてLooker Studioを使って簡単なBIツール連携を実現できます。
レポート画面.png

構成図

今回はkintoneのwebhook機能を利用して、レコードの追加/更新/削除のタイミングでCSVファイルを更新して、レポートに反映されるようにしてみます。
BI構成図.png

コード

CSV出力するプログラムをHTTPトリガーとしてCloud Functionsにデプロイします。前編main.pyを以下のように書き換えました。

main.py
import base64
import subprocess
import os
import datetime
import io
import pandas
from google.cloud import storage


# Triggered from a message on a Cloud Pub/Sub topic.
# @functions_framework.cloud_event
def entry_point_name(request):
    # Print out the data from Pub/Sub, to prove that it worked
    # print(base64.b64decode(cloud_event.data["message"]["data"]))

    domain = os.environ.get('DOMAIN')
    app_id = os.environ.get('APPID')
    token = os.environ.get('TOKEN')
    bucket_name = os.environ.get('BUCKET')

    args = f"record export --base-url https://{domain}.cybozu.com --app {app_id} --api-token {token}"
    res = subprocess.run([f"./cli-kintone {args}"], shell=True, encoding='utf-8', capture_output=True, text=True)
    # TODO "レコード番号"カラムを削除するがRDBにinsertする際もそれでよいか?
    record_num_deleted = delete_record_number(res.stdout)
    # とりあえず時間帯はGMT+9=JST決め打ち
    t_delta = datetime.timedelta(hours=9)
    jst = datetime.timezone(t_delta, 'JST')
    now = datetime.datetime.now(jst)
    fmt = now.strftime('%Y%m%d%H%M%S')
    # googlecloudapi ではCloudSQLへデータをインポートする際にヘッダ行を無視するオプションが存在しないため
    # CloudSQLへimportするためのヘッダなしCSVを保存する(再利用しないので後で消すこと)
    file_name_headless = f"{domain}/{app_id}/{fmt}_headless.csv"
    csv = '\n'.join(record_num_deleted.splitlines()[1:])
    upload_csv(csv, bucket_name, file_name_headless)
    # データをKintoneへimportする際はヘッダ付きCSVファイルを利用する
    file_name = f"{domain}/{app_id}/backup.csv"
    upload_csv(record_num_deleted, bucket_name, file_name)
    # TODO file_name_headless を指定するのではないか?
    # import_csv_to_rdb(bucket_name, file_name)
    # ...headless.csv は不要になったので削除する
    delete_csv(bucket_name, file_name_headless)

    request_json = request.get_json()
    if request.args and 'message' in request.args:
        return request.args.get('message')
    elif request_json and 'message' in request_json:
        return request_json['message']
    else:
        return f'Uploaded CSV completed.'


# CSVデータから"レコード番号"カラムを削除して返す
def delete_record_number(csv_buffer):
    df = pandas.read_csv(io.StringIO(csv_buffer))
    # Kintoneのデフォルト名が"レコード番号"のため
    res = df.drop(['レコード番号'], axis=1)
    buffer = io.StringIO()
    res.to_csv(buffer, index=False)
    return buffer.getvalue()


# 指定したファイルを削除する
def delete_csv(bucket_name, blob_name):
    storage_client = storage.Client()
    bucket = storage_client.bucket(bucket_name)
    blob = bucket.blob(blob_name)
    blob.delete()


# Cloud Storageにファイルをアップロードする
def upload_csv(csv, bucket_name, file_name):
    storage_client = storage.Client()
    bucket_name = storage_client.get_bucket(bucket_name)
    blob = bucket_name.blob(file_name)
    # TODO byte convertエラーが出るためstr()をしているがこの方法は新しい変数を生成して返しているため無駄が多い
    blob.upload_from_string(data=str(csv), content_type='text/csv')


# import_csv_to_rdb()は利用しないので省略

ポイント
・entry_point_name()の引数をrequestに変える
・CSVファイル名は固定(backup.csv)にする
・import_csv_to_rdb()の呼出しをコメントアウトする

デプロイスクリプト

前編deploy.shを以下のように書き換えました。

deploy.sh
#!/bin/sh

cp main.py deploy/main.py
cp requirements.txt deploy/
cp .env.yaml deploy/
cp cli-kintone-linux/cli-kintone deploy/
cd deploy

gcloud functions deploy  insert-to-rdbms-http \
--region=asia-northeast1 \
--runtime=python310 \
--entry-point=entry_point_name \
--env-vars-file=.env.yaml \
--trigger-http

ポイント
・HTTPトリガー、第1世代関数としてデプロイする

HTTPトリガー

下記スクリーンショットはGCPでHTTP関数としてデプロイされた状態です。
試しにトリガーURLをクリックすると画面に「Uploaded CSV completed.」と表示されれば成功です。「Error: Forbidden」と表示される場合は権限設定で「allUsers」に「Cloud Functions 起動元」のロールを割当てます。詳しい設定方法は公式ドキュメントのこのあたりが参考になります。
Cloud Functions http_trigger.png
成功すると指定バケットにbackup.csvが出力されます。次の設定で利用するため、CSVファイルのパスをクリップボードにコピーしておきましょう。
クリップボード.png

Looker Studio設定

次にbackup.csvをLooker Studioのデータソースに指定します。
Google Cloud Storage に接続するを参考に設定を進めます。

  1. Looker Studio にログインします。
  2. 左上にある 作成 をクリックして、[データソース] を選択します。
  3. リストから、[Google Cloud Storage コネクタ] を選択します。
  4. 承認を求めるメッセージが表示された場合は、[承認] をクリックします。
  5. データへのパスを入力します。
  6. 右上の [接続] をクリックします。

5の「データへのパス」は先程クリップボードにコピーしたCSVファイルのパスを貼り付けます。パスを入力すると右上の「接続」が有効になるので押します。
looker studio データソース接続.png
これでbackup.csvのフィールド(項目)が取り込まれました。
このまま「レポートを作成」を押します。
※レポートに追加するか確認されるので「レポートに追加」を押します。
looker studio フィールド設定.png

レポート画面になり、左にCSVデータが取り込まれています。
右の「グラフ」を押します。
データソース作成⑤.png
表の中から円グラフを選択すると、追加したデータが円グラフに変わります。右上の「表示」を押すとそのままレポートが作成されます。詳しい設定方法はレポートのチュートリアルを参照してください。
データソース作成⑥.png

Webhook設定

最後にkintoneでWebhookの設定を行います。
「Webhook URL」の部分に先程のHTTPトリガーURLを設定し、「通知を送信する条件」でレコード追加、編集、削除にチェックをつけて保存します。これで設定は完了です。
kintoneのWebhook設定.png

動作確認

kintoneのレコードの追加/更新/削除のタイミングでCSVファイルが更新されてレポートに反映されます。レポートに反映される更新頻度は1分単位で設定できます。詳しくは公式ヘルプのこちらをご確認ください。レポートの閲覧者が手動で更新することもできます。

最後に

今回の主な目的は既存DBにkintoneのデータを挿入する手段を探ることでした。その過程でcli-kintoneを活用すると随時バックアップやBIツール連携も行えることがわかりました。kintoneのCSV連携はとても奥が深いです。次はAPI連携に挑戦してみます。

続きの記事
Kintoneのデータをクラウドからバックアップ→リストアする処理を作りました!

3
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
3
1