Custom ActionでCSV(ZIP)ファイルをSJISで送信

※ 本投稿はLooker Advent Caledar 2021 7日目の記事となります。

Lookerから出力/送信されるCSVファイルは他システムで利用されることを考慮して、UTF8でエンコーディングされるようになっています。しかしながら、Microsoft ExcelはUTF8でエンコーディングされたCSVファイルをそのまま開くと文字化けしてしまうため、テキストエディタで開いて、エンコードを変更するか、Excelでファイルを開くメニューから順にウィザードに従って処理をする必要があります。CSVファイルがExcelに関連づけられていたりすると、ダブルクリックするとExcelが開いてしまってということで、CSVという形式に馴染みのない方もいらっしゃるのかもしれません。

そこで、今回は、Lookerが提供しているActionを利用して、Lookerから出力されるCSV形式のファイルをShift_JISやBOM付きCSVファイルに変換してメール送信してみたいと思います。

今回の開発に際しての前提条件:

  • Typescript/Javascriptをかけること
  • NodeJSを起動できる環境をお持ちであること
  • Yarnがインストールされていること
  • SendGridのアカウントがあること

それでは、早速始めていきましょう。

Custom Action Hubの開発

まずは、ローカルの開発環境にCustom Action Hub Exampleのソースをクローンします。次に、ZIPファイルを圧縮/解凍するライブラリと文字コード変換を行ってくれるライブラリを追加します。

yarn iconv-lite adm-zip @sendgrid/helpers @sendgrid/mail

Custom Actionのコードを記載していきます。

必要なライブラリのimportなど

import * as Hub from "looker-action-hub/lib/hub"

import * as helpers from "@sendgrid/helpers"
const AdmZip = require('adm-zip')
const iconv = require('iconv-lite')
const sgMail = require('@sendgrid/mail')

Custom Actionに必要な属性の追記

name = "send_with_encode"
label = "Send with Encoding"
description = "Send File with Re-Encoding"
supportedActionTypes = [Hub.ActionType.Cell, Hub.ActionType.Query, Hub.ActionType.Dashboard]
属性 概要
name Looker内で認識するCustom Action名
label ActionをLooker UI上で表示する際に利用するラベル
description Actionの概要を記載
supportedActionTypes このActionがサポートしているデータ送信元(cell, query, dashboard)

次に、Action単位での設定項目を定義します。今回は、SendGridを利用するため、SendGridのAPIキーとSendGridのメール送信者については、事前にメールアドレスが検証済みである必要があるため、事前に送信元を設定するようにしておきました。

  params = [
    {
      name: "send_grid_api_key",
      label: "Send Grid API Key",
      required: true,
      sensitive: false,
      description: "API key for sending email through Send Grid"
    },
    {
      name: "sender",
      label: "Mail Sender",
      required: true,
      sensitive: false,
      description: "Mail Sender Email Address"
    }
  ]

Action実行時(送信/スケジュール配信)の際に、入力するフォームの内容を定義します。

  async form() {
    const form = new Hub.ActionForm()

    form.fields = [
      {
        name: "recipient", 
        label: "Receipient Address", 
        required: true, 
        type:"string"
      },
      {
        name: "messaage", 
        label: "Message", 
        required: false, 
        type: "textarea",
      },
      {
        name: "encoding", 
        label: "Encoding", 
        required: true, 
        type: "select",
        options: [
          {name: "Shift_JIS", label: "Shift_JIS"},
          {name: "Windows-31j", label: "Windows-31j"},
          {name: "utf8b", label: "UTF8 with BOM"},
        ]
      },
      {
        name: "filenname",
        label: "File Name",
        required: true,
        type: "string"
      }
    ]

    return form
  }

実際に、ファイルを受信してからの変換処理については、以下のようになります。

まずは、zipファイルを解凍してファイルに分割します。

// read zip
const buffer = attachment.dataBuffer
const zip = new AdmZip(buffer)
const zipEntries = zip.getEntries()

const wZip = new AdmZip()

各ファイルをJavascriptの形式でデータを読み込み、選択されたエンコードでエンコーディングします。

zipEntries.forEach((entry: any) => {
  const decoded = iconv.decode(Buffer.from(zip.readAsText(entry), 'utf8'), 'utf8')
  let encFormat = request.formParams.encoding
  let addBom = false
  if (encFormat === "utf8b") {
    addBom = true
    encFormat = "utf8"
  }
  const encoded = iconv.encode(decoded, encFormat, {addBOM: addBom})
  wZip.addFile(entry.entryName, Buffer.alloc(encoded.length, encoded))
})

さらに、メール送信用に添付ファイルをbase64形式に変換します。

const attachZip = wZip.toBuffer().toString("base64")

最後に、SendGrid APIを利用してメール送信します。

const msg = new helpers.classes.Mail({
  to: request.formParams["recipient"],
  from: from,
  subject: subject,
  text: message,
  html: `<p>${message}</p>`,
  attachments: [
    {
     content: attachZip,
     filename: filename,
     type: 'application/zip',
     disposition: 'attachment',
     contentId: request.actionId
    }
   ]
})

let response = {success: true, message: "ok"}
try {
  await this.sendEmail(request, msg)
} catch (e) {
  response = {success: false, message: e.message}
}

return new Hub.ActionResponse(response)

ソースコード全体のサンプルコードは、こちらにあります。

Custom Action Hubのデプロイ

出来上がったプログラムは、こちらの手順に沿って、デプロイします。(なお、本サンプルのアクションは、Lookerから利用可能にするためには、APIキーを発行する必要があります)

https://github.com/looker/actions/blob/master/docs/deploying.md

なお、今回はVS CodeにGoogle Cloud Runのプラグインを利用して開発しています。そのため、APIキーを発行する際には、Dockerファイルに以下の記述を追記しておきます。

RUN yarn generate-api-key

上記を追記しておくと、deploy時のログにAPIキーが出力されていますので、こちらをLookerに登録時に記載します。(最初のデプロイ時に出力すれば、次回以降は不要になるので、コメントアウトしておきます)

また、上記デプロイ手順に記載がありますが、本プログラムを使ってデプロイして、起動する場合は、以下の環境変数の設定が必要になります。

  • ACTION_HUB_BASE_URL
  • ACTION_HUB_LABEL
  • ACTION_HUB_SECRET

Looker側の設定

次に、Looker側でCustom Actionを追加します。

Actionの画面を開くと、Lookerインスタンスで利用可能なアクションの一覧が表示されますので、画面最下部に表示される Add Action Hubのボタンをクリックします。そうすると、以下のようにAction HubのURLを入力する部分が表示されますので、デプロイしたAction HubのURLを入力し、Add Action Hubボタンをクリックします。

以下のようにエラーメッセージが表示されるかと思いますので、Configure Authorizationをクリックして、APIキーを入力します。

正しく、APIキーが入力されると以下のように表示されます。

次に、追加されたアクションはEnabledになっていませんので、リストの横にあるEnableボタンをクリックして、Send GridのAPIキーと、Send Grid側で認証されたメールアドレスをMail Senderへ入力します。

正しく設定されれば、Test Actionの部分が以下のように表示されます。

これで、送信する準備は整いましたので、以下のようにダッシュボードのSchedule deliveryメニューを選択すると、今回追加したアクションの送信用ダイアログが表示されます。

いかがでしたでしょうか。Lookerが提供しているAction Hubの機能は、今回のようにAction Hubサンプルコードを利用して、簡単に独自のActionを構築可能になっています。また、APIキーを利用することで、特定のLookerインスタンスからのみのアクセスを受け付けるなど、セキュリティ面も確保しつつ、サービスを提供することも可能になります。

みなさまがお使いのシステム/サービスと連携することで、さらにLookerの活用の幅が広がりますので、試してみていただければと存じます。

上記でご紹介したサービス/APIについては執筆時点での情報を利用していますため、仕様が変更になっている可能性があります。> > また、ご紹介しているサンプルコードに関してのお問い合わせは、LookerのAction APIに関する仕様を除いて、サポートチームでの対応はいたしかねます。

こちら参考にさせていただき、実行するactionは異なりますが
同様の手順で「Custom Action Hubのデプロイ」まで準備をしました。

Herokuでアクションハブを実行するため以下コマンドを実行したところエラーが出力されてしまったのですが、上記手順の記載してある内容以外に他に何か設定するべき内容があればご教示いただけますと幸いです。

・実行コマンド:heroku run yarn generate-api-key
・エラー:error Command “generate-api-key” not found.

こちら、package.jsongenerate-api-keyの記載がないのではと思います。

"scripts": {
    "start": "./node_modules/.bin/ts-node ./src/index.ts",
    "generate-api-key": "./node_modules/.bin/ts-node ./bin/generate_key.ts"
  },

また、api-keyを発行するソースも含まれていないのではないかと思います。

https://github.com/shinkawa2002/custom_action_sample/tree/main/bin

ご教示いただいた内容で解決することができました。ご回答ありがとうございました!

原因としてはapi-keyを発行するソースが含まれていなかったようです。