Looker Custom Actionを作成する

<h6>当エントリは2020年アドベントカレンダー『Looker Advent Calendar 2020』の20日目のエントリとなります。</h6>

- [Looker Advent Calendar 2020 - Qiita](https://qiita.com/advent-calendar/2020/looker)


こんにちは。株式会社電通国際情報サービスのkumakuraです。今回はLookerの外部連携機能である**Action**について記事を書いていこうと思います。また、実際に作成した[Microsoft Teams]( https://www.microsoft.com/ja-jp/microsoft-365/microsoft-teams/group-chat-software)とビジネス版LINE「[LINE WORKS]( https://line.worksmobile.com/jp/)」のActionについても記事を作成しましたので、そちらもご覧になっていただければと思います。

- [LINE WORKSへメッセージを送るLooker Action](https://qiita.com/kumakura/items/48d14b31fda4e8c46c0b)
- [Microsoft Teamsへメッセージを送るLooker Action](https://qiita.com/kumakura/items/3c2973b513ac5bb44519)

#目次
[1. Action/Action Hubとは](#1-actionaction-hubとは)
[2. Custom Actionの開発方法](#2-custom-actionの開発方法)
[3. 公式フレームワーク/Action APIで開発する](#3-公式フレームワークaction-apiで開発する)
[4. LookerインスタンスにAction Hubを登録する](#4-lookerインスタンスにaction-hubを登録する)
[5. Microsoft Teams/LINE WORKSのActionを作成してみました](#5-microsoft-teamsline-worksのactionを作成してみました)
[6. おわりに](#6-おわりに)




#1. Action/Action Hubとは

BIツールLookerでは分析して得た結果や条件に応じて、**Action Hub**と呼ばれるサーバを経由して外部サービスにデータを連携することができます。
Looker内ではそれら外部サービス連携機能を**[Action]( https://ja.looker.com/platform/actions)**と呼びます。

Looker公式で[TwilioやSlackといった**Action**が数十種類公開](https://docs.looker.com/ja/admin-options/platform/actions)されており、Lookerインスタンス内で有効にするだけで簡単に利用できます。

ただ、「一覧にないサービスと連携したい！」「独自のシステムと連携したい！」
といった場合は**独自にAction（以降Custom Action）**を開発することも可能です。

#2. Custom Actionの開発方法

Custom Actionを利用したい！となった場合、大きく分けて3つ方法があります。

1. **公式が提供しているNode.jsのフレームワークを用いて、Lookerのgithubにcontributeする**
Looker公式のAction Hubは[Github上で公開](https://github.com/looker/actions/tree/master/)されております。
アクションを作成し、プルリクエストを送り、Lookerからレビュー/検査を通ったアクションは全Lookerユーザが利用できる形で公開され、公式のActionの一つとなります。

2. **公式が提供しているNode.jsのフレームワークを使い、プライベートなAction Hubを建てる**
全ユーザに公開したくない場合は独自にAction Hubサーバを用意し、Lookerインスタンス上で登録することでAction Hubとして利用することが出来ます。
[Node.jsのフレームワークのテンプレート](https://github.com/looker/custom-action-hub-example)も公開されているので利用することで、簡単に開発が可能です。

3. **Action APIを使用して、プライべートなAction Hubを建てる**
公式のフレームワークを利用せずに、Action APIと呼ばれるエンドポイントを直接実装することで、Action Hubサーバを建てることもできます。


　
以下はActionを開発し、Lookerインスタンスで利用するまでのワークフローです。
![looker action flow](https://docs.looker.com/assets/images/develop-action-hub-workflow-612.png)
引用:https://docs.looker.com/sharing-and-publishing/action-hub#adding_user_attributes_to_custom_actions

1が上のフローにおける*"Build a Custom Action, Publish to the Looker Action Hub Server"*に該当し、2、3が*"Build a Custom Action, Publish to Private Action Hub Server"*に該当します。

1、2の場合、フレームワークは`Typescript`で書かれているので準拠する必要がありますが、3の場合、APIを叩くことができればどの言語でも構いません。
また、フレームワーク内部でもAction APIを利用しており、どちらで開発しても作成できる機能に違いはありませんので、好きな方法で開発していただければと思います。

#3. 公式フレームワーク/Action APIで開発する
2章で紹介したように、Custom Actionを利用する場合、「公式フレームワークを利用する」ケースと「Action APIを利用する」ケースの二つが存在します。3章ではそれぞれ簡単に開発方法について説明していきたいと思います。
なお、公式フレームワークはAction APIをラップしたような関係であるため、Actionを定義するときに必要なパラメータはほぼ同じなので、説明の都合上、Action APIの方から説明いたします。

##3.1. Action API

Action APIとはLookerからのアクションを受け入れるIncoming WebhookのようなAPIです。
LookerインスタンスからActionに関係する操作を行ったとき、操作に応じてそれぞれ指定のエンドポイントにリクエストが送られます。サーバ側としてはリクエストに応じて特定のレスポンスを返す必要があります。
エンドポイントは基本的に`list`、`execute`、`form`等になります。
それらエンドポイントを搭載したサーバを用意し、Lookerインスタンスに登録することでAction Hubとして利用することが可能になります。


次に`list`、`execute`、`form`でどのようなHTTPリクエストが発生し、どのようなHTTPレスポンスを返す必要があるのかサンプルを交えながら説明して行きます。
※今回、サンプルとして取り上げた項目以外にも多くの設定項目がありますので、[公式のドキュメント](https://github.com/looker/actions/blob/master/docs/action_api.md)をご参考ください


### ● `list`エンドポイント

Actionをインスタンスに設定する時や、実際にセル、クエリ(Explore,Look)、ダッシュボードからActionを実行する時に空のPOSTが送られるエンドポイントです。
レスポンスとしてActionの定義情報を返す必要があります。
また、Action"HUB"と呼ばれるように一つのエンドポイントで複数のActionを提供することが出来ます。
後述する **`execute`エンドポイント/`form`エンドポイント** 先などもこの際に定義します。


#### Sample HTTPレスポンス

```json
{
    "label": "Action hub label",
    "integrations": [
      {
        "name": "action_name",
        "label": "Action Label",
        "supported_action_types": ["cell", "query", "dashboard"],
        "url": "https://XXXX/action/execute",
        "form_url": "https://XXXX/action/form",
        "icon_data_uri": "data:imageXXXXXX",
        "required_fields": [
          {
            "tag": "test",
          },
        ],
        "params": [
          {
            "name": "param_name",
            "label": "param label",
            "required": true,
          },
        ],
        "supported_formats": ["txt", "wysiwyg_png"],
        "supported_visualization_formattings": ["apply"],
        "supported_download_settings": ["url"],
      },
    ],
}
```

####Sampleの説明

|キー|説明 |
| ---------------------- | ---- |
| supported_action_types | Actionを実行できるフォーマットを定義します。<br />cellの場合は1つの値から、queryの場合はexplore/viewから、dashboradの場合はダッシュボードから<br />Actionを実行することができます。 |
| url            | 後述する**executeエンドポイント**を指定します。 |
| form_url | 後述する**formエンドポイント**を指定します。 |
| icon_data_uri | Actionの一覧画面に表示されるアイコンです。Data URI形式で定義します。 |
| required_fields | supported_action_typeにcellが指定されている場合、<br />required_fieldsで定義したtagが付けられているdimension/fieldのみActionを実行することができます。 |
| params | Actionを有効化する際にユーザに入力させる項目を定義します。<br />アクセストークンといったAction全体で使用したい定義情報をユーザに入力させる目的で利用されます。 |
| supported_formats | Actionが実行された際にどの形式で分析データを送るかを定義できます。<br />queryの場合はcsv、json、txt、html等から選択でき、dashboardの場合は画像形式が選択できます。 |


---


### ● `form`エンドポイント

Actionを選択したときに空のPOSTが送られるエンドポイントで、上記の`List`エンドポイントでActionを定義する際に`form_url`で指定したエンドポイントとなります。
Actionを利用する際にユーザに入力させるフォーム情報を返す必要があります。

#### Sample HTTPレスポンス

```json
[
    {
      "name": "form_name",
      "label": "form",
      "description": "form description",
      "type": "textarea",
      "required": true,
    },
]
```

#### Sampleの説明
|キー|説明 |
| ------ | ---- |
| type | ユーザに入力させるフォームの形式を選択できます。"text","textarea","select"から選択できます。<br />"select"とはセレクトボックス形式のことで、選択した場合_options_キーを追加し、セレクトボックスの値を定義する必要があります。<br />~~複数選択は指定できません~~ |
| required |項目を必須にすることができます|

---


### ● `execute`エンドポイント

Actionが実行する時に選択したセル、クエリ、ダッシュボードの情報がPOSTで送られるエンドポイントです。
このエンドポイントから受け取ったデータを元に具体的な処理を行います。

### Sample HTTPリクエスト(CELLの場合)

```typescript

{
  type: 'cell',
  scheduled_plan: null,
  attachment: null,
  data: {
    param:'param',
    value:'value'
  },
  form_params: { form: 'form' },
}
```

#### Sampleの説明
- `data`にはユーザがActionを有効化した際に入力した項目です。
- `value`は選択したセルの値が入ります。
- `form_params`はActionを実行する際にユーザが入力したフォームの値です。


### Sample HTTPリクエスト(QUERYの場合)

```typescript
{
  type: 'query',
  scheduled_plan: {
    scheduled_plan_id: 19,
    title: 'sample title',
    type: 'Query',
    url:
      'https://lookerinstance.jp.looker.com/explore/model/query',
    query_id: 1341,
    query: {
      id: 1341,
      view: 'view name',
      fields: [Array],
      pivots: null,
      fill_fields: null,
      filters: null,
      filter_expression: null,
      sorts: [Array],
      limit: '10',
      column_limit: null,
      total: null,
      row_total: null,
      subtotals: null,
      vis_config: {},
      filter_config: null,
      visible_ui_sections: null,
      slug: 'xxxxxxx',
      dynamic_fields: '[]',
      client_id: 'xxxxxxx',
      share_url: 'https://lookerinstance.jp.looker.com/x/XXXXXX',
      expanded_share_url:
        'https://lookerinstance.jp.looker.com/explore/model/XXXXXX',
      url:'/explore/model/XXXXXX',
      query_timezone: 'America/Los_Angeles',
      runtime: null,
      has_table_calculations: false,
      model: 'model',
    },
    filters_differ_from_look: null,
    download_url:
    'https://lookerinstance.jp.looker.com/download/XXXXXX',
  },
  attachment: null,
  data: {
    param:
      'param',
  },
  form_params: { message: 'test' },
}
```

#### Sampleの説明

- `List`エンドポイントで　` supported_download_settings`に`url`を設定していた場合、`download_url`パラメータに一度のみ有効なデータのダウンロードリンクが添付されますが、`supported_download_settings`に何も設定していない場合は`attachment`パラメータにデータの情報がそのまま添付されます

      - attachment以下にデータが入る例(textの場合)

    ```typescript
      attachment: {
        mimetype: 'text/plain',
        extension: 'txt',
        data:'xxxxx',
      }
    ```

    

### Sample HTTPリクエスト(DASHBOARDの場合)

```typescript
{
  type: 'dashboard',
  scheduled_plan: {
    scheduled_plan_id: 99,
    title: 'Sample Dashboard',
    type: 'Dashboard',
    url:
      'https://lookerinstance.jp.looker.com/dashboards/?XXXXXXXX',
    query_id: null,
    query: null,
    filters_differ_from_look: null,
    download_url: null,
  },
  attachment: {
    mimetype: 'image/png;base64',
    extension: 'png',
    data: 'XXXXXXXXXXXXXXXXXXXXXXXXXX',
  },
  data: {
    param:'param',
  },
  form_params: { form: 'form' },
}
```

#### Sampleの説明
- `attachment`パラメータの中にbase64でファイルが添付されます。(上記の例はpng)



---



##3.2. Looker公式Node.jsフレームワーク
「公式としてCutsom Actionを公開する」または「公式のフレームワークのテンプレートを用いて開発する」場合は以下の手順に則っていただければと思います。

#### 1.フォルダを配置
公式のgithubからフォークしていただくか、テンプレートをインストールしていただき、
`actions > src > actions` 配下に追加したいActionのフォルダを追加します。
[参考] https://github.com/looker/actions/tree/master/src/actions

#### 2.フォルダの配下にコードを配置します。

フォルダの配下にActionを実装したクラスを配置します。下記サンプルコードを見ていただけると分かるかと思いますが、Action APIの`list`、`execute`、`form`を1つにまとめたようなクラスとなっております。

##### Sample Code
 
```typescript
import * as Hub from "../../hub";

export class SampleAction extends Hub.Action {
  name = "action_name";
  label = "action_label";
  iconName = "folder/sample.png";
  description = "sample description";
  supportedActionTypes = [Hub.ActionType.Query];
  supportedFormats = [Hub.ActionFormat.Csv];
  supportedFormattings = [Hub.ActionFormatting.Unformatted];
  supportedDownloadSettings = [Hub.ActionDownloadSettings.Push];
  requiredFields = [];
  params = [];

  async execute(req: Hub.ActionRequest) {

    //
    // 具体的な処理
    //

    return new Hub.ActionResponse(response);
  }

  async form(req: Hub.ActionRequest) {
    const form = new Hub.ActionForm();
    form.fields = [];
    form.fields.push({
      label: "form label",
      name: "form_name",
      required: true,
      type: "string",
    });

    return form;
  }
}

Hub.addAction(new TeamsAction());

```
内容は、細かな違いはありますが、Action APIで定義した内容と似ていますね。

#### 3.(公式にContributeする場合)Testクラスを作成します

公式にContributeする場合Testクラスを作成する必要があります。
公式フレームワークで採用しているテストのライブラリは`mocha`+`chai`+`sinon`となっております。

#### 4.indexの編集

`actions > src > index.ts`、`actions > test > test.ts`
にそれぞれ作成したAction/Testクラスのソースコードのパスを通します。


以上でActionの実装は完了なのですが、注意点として公式のNode.jsのフレームワーク利用したAction HubをLookerインスタンスに登録する際、APIキーを作成し、環境変数として組み込む必要があります。
詳細は[ドキュメント](https://docs.looker.com/ja/sharing-and-publishing/action-hub)をご確認ください

#4. LookerインスタンスにAction Hubを登録する

3.でソースコードが完成したら、任意の環境にデプロイしましょう。
公式ではHerokuで説明されておりますが、例えばAWS上で構築していただいても全く問題ありません。

Action Hubサーバをデプロイしたら、Lookerインスタンスに登録しましょう！
管理タブからActionを選択肢、[Add Action Button]を選択し、作成したAction HubのURLを入力することでCustom Actionを利用できるようになります。
Action APIを利用している場合はここに`list`エンドポイントのURLを入力してください。
（公式フレームワークを使用する場合、作成されたAPIキーを`Authorization Token`に入力する必要があります。）
![Looker Page.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878249/7dd614a4-9f47-ecd1-6a19-bf15a24f7ddb.png)

以上が簡単ではありましたがAction Hubの構築方法となります。

#5. Microsoft Teams/LINE WORKSのActionを作成してみました
（量が多くなってしまうので、記事を分割しました）
前述のLookerが提供しているNode.jsのフレームワークを用いて**Microsoft TeamsのAction**とAction APIを用いて**LINE WORKSのAction**を作成してみました。

- [LINE WORKSへメッセージを送るLooker Action](https://qiita.com/kumakura/items/48d14b31fda4e8c46c0b)
- [Microsoft Teamsへメッセージを送るLooker Action](https://qiita.com/kumakura/items/3c2973b513ac5bb44519)

具体的な実装例なども併せて紹介しておりますので、ご覧ください！

#6. おわりに
今回は簡単にAction Hubの構築方法について記載してきましたが、他にもAction Hubには多くの機能があり、OAuthを用いてユーザ個別の処理を実装したり、formをユーザの入力を元に動的に変更させたり色々できますのでドキュメントを是非ご覧ください。


公式のActionとして公開する場合、全Lookerユーザが利用するので汎用的な設計を心がける必要がありますが、独自でAction Hubサーバを立てる場合、要件に特化した連携機能を作成することができますので、是非皆さんもAction機能を開発してみて、Lookerをデータプラットフォームとして有効活用していきましょう！
ここまで読んでいただきありがとうございました。