LoginSignup
2

More than 1 year has passed since last update.

Microsoft Teamsへメッセージを送るLooker Action

Last updated at Posted at 2020-12-18

こんにちは。株式会社電通国際情報サービスのkumakuraです。
今回はBIツールLookerの外部連携機能であるActionMicrosoft Teams(以下「Teams」)にメッセージを送る連携機能を作成してみました。

目次

1. Action/Action Hubとは
2. Microsoft Teams APIのIncoming Webhookについて
3. 今回作成したActionについて
4. 利用イメージ
5. 終わりに

1. はじめに

BIツールLookerでは分析して得た結果や条件に応じて、Action Hubと呼ばれるサーバを経由して
外部サービスにデータを連携することができます。
Looker内ではそれら外部サービス連携機能をAcitionと呼びます。

Looker公式でTwilioやSlackといったActionが数十種類公開されていますが、プログラミングをすることで独自にActionを開発することも可能です。

独自のActionを開発する場合、
①. 「公式Node.jsフレームワークを用いる」方法
②. 「Action APIを自前で実装する」方法
の2つ方法があるのですが、今回は①公式Node.jsフレームワークを採用して、Teamsへ簡単なアラートを送るActionを開発していきたいと思います。

2. Microsoft Teams APIのIncoming Webhookについて

ソースコードの解説の前に、今回使用したMicrosoft Teams APIのIncoming Webhookについて少し解説します。

Teamsにサーバからメッセージを送りたい場合、アプリを登録してアクセストークンを取得して〜とフローを踏む方法もありますが、シンプルに「特定のチャネルに対してメッセージを送りたい」場合、Incoming Webhookをチャネルに設定するのが一番手軽な方法かと思います。

Incoming Webhook(受信Webhook)はチャネルに設定できるコネクタの1つで、生成されたエンドポイントに対してOffice 365 コネクタ カード形式またはアダプティブカード形式のJSONを送ることでbotライクなメッセージを投稿することができます。

Incoming Webhookの設定方法

  1. Incoming Webhook を追加したいチャネルの、上部のナビゲーションバーから [コネクタ] を選択
    1.png

  2. Incoming Webhook を検索し、[構成] ボタンを選択。(チームでIncoming Webhookが追加されていない場合、[追加]ボタンを押下してチームにアプリを追加してください。
    2.png

  3. 名前を入力し、オプションで Webhook 用の画像アバターをアップロードし、[作成]ボタンを押下します。
    3.png

  4. URLが生成されます。生成されたエンドポイントにOffice 365 コネクタ カード形式またはアダプティブカード形式のJSONを送ることでメッセージを送信できます。
    4.png

  5. [完了]ボタンを選択します。

3. 今回作成したActionについて

今回、LookerからTeamsのIncoming Webhookに対してタイトルと本文、Lookerへのリンクを含んだメッセージを送るActionを作成していきます。
LookerではActionをスケジューリング機能を用いて日次で実行したり、フィルターを設定することで閾値を超えたデータが発生した場合に実行をすることができます。(勿論オンデマンドで実行もできます。)
今回のActionは「月初に特定のレポートを通知する」「異常値が発生した場合通知する」ユースケースを想定して作成しました。

また、今回はLooker公式のNode.jsフレームワークを用いてActionを作成したいと思います。
フレームワークはTypeScriptで書かれています。

ディレクトリ構成

公式フレームワークを用いてActionを開発する場合、(root) > src > actions配下にフォルダを配置し、その中にTypeScriptのソースコードを配置します。
TeamsのActionを開発した場合ディレクトリ構成は次のようになります。(かなり省略しています)

actions/
 ├ bin/
 ├ ci/
 ├ docs/
 ├ public/
 ├ src/
 │ ├ actions/
 │ │ └ teams
 │ │   ├ README.md
 │ │   ├ teams.png (アイコン画像)
 │ │   ├ teams.ts  (ソースコード本体)
 │ │   └ test_teams.png (テストクラス)
 │ ├ api_types/
 │ ├ crypto/
 │ ├ hub/
 │ ├ server/
 │ ├ xpc/
 │ └ boot.ts
 ├ test/
 ~

 
 
 

ソースコード

Actionの定義情報を実装する

まずはActionの定義情報を実装していきます。

// teams.ts

import * as winston from "winston";
import * as Hub from "../../hub";
import * as httpRequest from "request-promise-native";

export class TeamsAction extends Hub.Action {
  name = "teams_incomingwebhook";
  label = "Teams - Incoming Webhook";
  iconName = "teams/teams.png";
  description = "Send data to Teams Incoming webhook";
  supportedActionTypes = [Hub.ActionType.Query, Hub.ActionType.Dashboard];
  supportedFormats = [Hub.ActionFormat.Csv, Hub.ActionFormat.WysiwygPng];
  supportedFormattings = [Hub.ActionFormatting.Unformatted];
  supported_visualization_formattings = [
    Hub.ActionVisualizationFormatting.Noapply,
  ];
  params = [];

  async execute() {
  }

}

Hub.addAction(new TeamsAction());

今回はアラートでレポートをTeamsに送るユースケースを想定しましたので、Actionを実行できるタイプはQuery(Explore,Look)とDashboardに限定しました。また、Teamsへはデータの送信ではなく、LookerのLook/Explore/DashboardのURLを添付するので(Incoming Webhookの場合データ送付が難しい)ため、データのフォーマットもCsv(queryの場合)とWysiwygPng(Dashboard)に限定しました。
なお、ここで何も選択しない場合、ユーザはLookerで使用できるフォーマットの全てを選択することができます。
今回、QueryとDashbordに対応したフォーマットを1つだけ選択することでユーザにフォーマットを意識させることなく指定のフォーマットでActionを実行させることができます。

それぞれ対応したデータのフォーマットは以下のようになります
- Query : Txt,Csv,InlineJson,Json,JsonLabel,JsonDetail,JsonDetailLiteStream,Xlsx,Html
- Dashboard : WysiwygPdf,AssembledPdf,WysiwygPng,CsvZip

 
 

form( )を実装する

次にTeamsへタイトルと本文を含んだメッセージをユーザへ入力させるフォームを作成していきます。
フォームの定義は上記クラスの中でformメソッドを用意し、その中で実装していきます。


// teams.ts

  async form() {
    const form = new Hub.ActionForm();
    form.fields = [];
    form.fields.push({
      label: "Webhook URL",
      name: "webhookUrl",
      required: true,
      type: "string",
    });
    form.fields.push({
      label: "Title",
      name: "title",
      required: true,
      type: "string",
    });
    form.fields.push({
      label: "Text",
      name: "text",
      required: false,
      type: "textarea",
    });
    form.fields.push({
      label: "Attach Meta Data",
      description: "attach meta data(type,title,model,view)",
      default: "false",
      name: "isAttached",
      required: false,
      type: "select",
      options: [
        { name: "false", label: "false" },
        { name: "true", label: "true" },
      ],
    });

    return form;
  }

ユーザに入力させるフォームとして①TeamsのIncoming Webhookのエンドポイント、②メッセージのタイトル、③メッセージの本文、④メタデータの送信の可否 を考えてみました。
テキスト形式の入力以外にもセレクトボックスからユーザに値を入力させることができます。(上記のAttach Meta Data

なお、Lookerインスタンス全体で1つのIncoming Webhookを利用する場合(1つのチャネルにだけ送信する場合)、Actionの定義の段階でparamを用意し、Actionを有効化する際にユーザにWebhookのURLを入力させることで、実行のたびに毎回URLを入力せずに済むのですが、Lookerインスタンス全体で複数のチャネルに対してメッセージを送るケースが多そうな事と、今回想定しているユースケースはアラート/スケジュール利用なので頻繁にフォームから実行されるケースが少なさそうな事を考えて、Actionを実行する際に毎回設定する方針を採用しました。

 
 

execute( )を実装する

 
最後に、Actionが実行された後の具体的な処理を実装していきましょう!
Actionが実行された後の処理はexecuteメソッド内で実装していきます。


// teams.ts

  async execute(req: Hub.ActionRequest) {
    let response = { success: true, message: "success" };

    if (!req.formParams.webhookUrl) throw new Error("Need a webhookUrl");
    if (!req.formParams.title) throw new Error("Need a title");
    if (!req.formParams.isAttached) throw new Error("Need a attach flag");
    if (!req.scheduledPlan)
      throw new Error("Couldn't get data from scheduledPlan");

    const webhookUrl = req.formParams.webhookUrl;
    const title: string = req.formParams.title;
    const text: string =
      req.formParams.text === undefined
        ? ""
        : req.formParams.text.replace(/\n/g, "\n\n");

    const resCard = {
      "@type": "MessageCard",
      "@context": "http://schema.org/extensions",
      themeColor: "5035b4",
      summary: "Looker Reports",
      title: title,
      text: text,
      sections: [] as any,
      potentialAction: [
        {
          "@type": "OpenUri",
          name: "View in Looker",
          targets: [{ os: "default", uri: req.scheduledPlan.url }],
        },
      ],
    };

    if (req.formParams.isAttached === "true") {
      const facts = [];
      facts.push({
        name: "Type :",
        value: req.scheduledPlan.type,
      });
      facts.push({
        name: "Title :",
        value: req.scheduledPlan.title,
      });
      if (req.type === Hub.ActionType.Query && req.scheduledPlan.query) {
        facts.push({
          name: "Model :",
          value: req.scheduledPlan.query.model,
        });
        facts.push({
          name: "View :",
          value: req.scheduledPlan.query.view,
        });
      }
      resCard.sections.push({
        facts: facts,
      });
    }

    const option = {
      url: webhookUrl,
      json: resCard,
    };

    try {
      const result = await httpRequest.post(option).promise();
      if (result !== 1) {
        throw new Error(result);
      }
    } catch (e) {
      response = { success: false, message: e.message };
      winston.error(e.message);
    }
    return new Hub.ActionResponse(response);
  }

ユーザにフォームで入力してもらった情報を元にIncoming Webhookに対してPOSTを送っております。
また、POSTで送るJSONの形式としてOffice365コネクタカードの形式を採用しました。

処理の内容は至ってシンプルで、ユーザに入力していただいたIncoming WebhookのURLに対して、タイトル・本文・Lookerへのリンク(・メタデータ)をOffice365コネクタカードの形式に整形を行いPOSTする、という内容になっています。

4. 利用イメージ

Lookerでユーザに入力させるフォームは次のようになります。
image1.png

上の画像のようにユーザがフォームを入力した場合、Teamsには次のようなメッセージが投稿されます。
スクリーンショット 2020-12-18 3.41.49.png
今回、フォームの段階でAttach Meta Dataがtrueになっているので、Teams上のメッセージにメタデータが添付されています。

5. 終わりに

今回のTeamsのActionは、Looker公式のリポジトリにプルリクエストを送ることを想定して、公式のフレームワークで作成しています。
プルリクエストが承認された場合、皆さんもLookerから利用できるようになりますので、(シンプルな機能ではありますが)是非利用してみてください。
今回の記事が皆さんのAction開発の一助になれれば幸いです。ここまで読んでいただきありがとうございました


追記(2021/12/10)

いつの間にかLooker公式サイトでも紹介されてました
https://looker.com/product/new-features
image.png

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
2