こんにちは。株式会社電通国際情報サービスのkumakuraです。
今回はBIツールLookerの外部連携機能であるActionでMicrosoft 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の設定方法
-
Incoming Webhook を検索し、[構成] ボタンを選択。(チームでIncoming Webhookが追加されていない場合、[追加]ボタンを押下してチームにアプリを追加してください。
-
URLが生成されます。生成されたエンドポイントにOffice 365 コネクタ カード形式またはアダプティブカード形式のJSONを送ることでメッセージを送信できます。
-
[完了]ボタンを選択します。
#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でユーザに入力させるフォームは次のようになります。
上の画像のようにユーザがフォームを入力した場合、Teamsには次のようなメッセージが投稿されます。
今回、フォームの段階でAttach Meta Dataがtrueになっているので、Teams上のメッセージにメタデータが添付されています。
#5. 終わりに
今回のTeamsのActionは、Looker公式のリポジトリにプルリクエストを送ることを想定して、公式のフレームワークで作成しています。
プルリクエストが承認された場合、皆さんもLookerから利用できるようになりますので、(シンプルな機能ではありますが)是非利用してみてください。
今回の記事が皆さんのAction開発の一助になれれば幸いです。ここまで読んでいただきありがとうございました
追記(2021/12/10)
いつの間にかLooker公式サイトでも紹介されてました
https://looker.com/product/new-features