CloudWatchのカスタムウィジェットのLambda書いてみた
どうも。CX事業本部Delivery部のえーたん(@eetann092)です。
先日、CloudWatchのダッシュボードでLambdaの実行結果をカスタムウィジェットとして表示できることを知りました。せっかくなので素振りしました。今回はクリックしたボタンに応じて表示内容を変えるウィジェットを作成しました。
ボタンクリック時には確認画面も表示します。
カスタムウィジェットの作成
LambdaはCDKでNodejsFunctionを使って作成しました。
import * as cdk from "aws-cdk-lib"; import { Construct } from "constructs"; import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs"; export class CustomWidgetLambdaSampleStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); new NodejsFunction(this, "CustomWidgetLambda", { entry: "lambda/custom-widget-function.ts", }); } }
以下は、Lambdaに書いた処理の全体が分かるように一部を省略して書いたものです。
const DOCS = ` Markdownでドキュメントが書ける。 `; export const handler = async (event: Event, context: Context) => { if (event.describe) { return DOCS; } // 送信された値を取得する const person = event.person || ""; const go = event.go || false; return ` <div> aタグとcwdb-actionを使ってクリック時に送信 送信された値から作成したテキストを表示 </div> `; };
長かったのでフルバージョンは以下の折りたたみの中に書きました。
クリックで展開できます
type Event = { describe: boolean; widgetContext: any; person: string; go: boolean; }; type Context = { invokedFunctionArn: string; }; const DOCS = ` ## custom-widget-function Markdownでドキュメントを書くことができます。 **太字**、*イタリック*、~~打ち消し線~~、\`インラインコード\`などなど * リスト * リスト * ネスト * リスト 1. 番号付きのリスト 2. 番号付きのリスト 1. ネスト 3. 番号付きのリスト [リンク](https://dev.classmethod.jp/author/eetann/) ![画像](https://raw.githubusercontent.com/eetann/choomame/main/public/icons/icon-128x128.png) --- \`\`\` python print("piyopiyo") \`\`\` ### テーブルも書ける name | ok ---|--- Kerry | true Johnny | false Goro | false | name | ok | |---|---| | Kerry | true | | Johnny | false | | Goro | false | `; export const handler = async (event: Event, context: Context) => { if (event.describe) { return DOCS; } const timestamp = new Date(); const person = event.person || ""; const go = event.go || false; let response = ""; const people = [ { name: "Kerry", ok: true, cls: "btn kerry" }, { name: "Johnny", ok: false, cls: "btn btn-primary" }, { name: "Goro", ok: false, cls: "btn" }, ]; const rows = people .map(({ name, ok, cls }) => { return ` <tr> <td>${name}</td> <td> <a class="${cls}">選ぶ</a> <cwdb-action action="call" endpoint="${context.invokedFunctionArn}" confirmation="本当に${name}を選びますか?"> { "person": "${name}", "go": ${ok} } </cwdb-action> </td> </td> </tr> `; // }); }) .join(""); if (person) { if (go) { response = `${person}「着いたら連絡する」`; } else { response = `${person}「行けたら行く」`; } } const _event = event; _event.widgetContext.accountId = 123456789012; return ` <div> <table> ${rows} </table> <p>${timestamp}</p> <p>${response}</p> <pre>${JSON.stringify(_event, null, 2)}</pre> </div> <style> .kerry { color: white !important; background-color: black !important; } </style> `; };
1つずつ区切って説明します。
まず、最初のevent.describe
を使ったif文は、ドキュメントの表示です。
if (event.describe) { return DOCS; }
ドキュメントはMarkdownで記述でき、カスタムウィジェットの追加や編集時に表示されます。
実際に書いてみたところ、文字の装飾やリストの他、画像も普通に表示できました。
次に、Lambdaのハンドラの第1引数event
から、person
とgo
を変数に代入しました。
event.person
とevent.go
は、HTMLのタグcwdb-action
を使って送信した値が入ります。
const person = event.person || ""; const go = event.go || false;
タグcwdb-action
は、直前の要素が選択された時の動作を決めるものです。
今回の例では、直前の要素a
タグのクリック時の動作を「カスタムウィジェットを表示するLambda(=同じLambda)の呼び出し」にしています。実際の役割はcwdb-action
タグの中に書いた値の送信です。
<a class="${cls}">選ぶ</a> <cwdb-action action="call" endpoint="${context.invokedFunctionArn}" confirmation="本当に${name}を選びますか?"> { "person": "${name}", "go": ${ok} } </cwdb-action>
confirmation
属性を使うことで確認画面を表示させました。
aタグのclass
では、ボタンとして表示するための指定をしました。
カスタムウィジェットで用意されているスタイルbtn
、btn-primary
のほか、style
タグで独自にスタイルを設定できます。
preタグを使って、Lambdaのハンドラの第1引数event
の中身も表示してみました。JSON.stringify()に第2引数、第3引数があることを初めて知りました。
<pre>${JSON.stringify(_event, null, 2)}</pre>
以下が表示された内容です。タグcwdb-action
で送信したperson
やgo
の他、widgetContext
キーにたくさんの値が入っています。ウィジェットのサイズも分かるようです。
{ "person": "Kerry", "go": true, "widgetContext": { "dashboardName": "custom-widget-lambda-sample", "widgetId": "widget-1", "domain": "https://ap-northeast-1.console.aws.amazon.com", "accountId": 123456789012, "locale": "ja", "timezone": { "label": "Local", "offsetISO": "+09:00", "offsetInMinutes": -540 }, "period": 300, "isAutoPeriod": true, "timeRange": { "mode": "relative", "start": 1668725221893, "end": 1668736021893, "relativeStart": 10800004 }, "theme": "light", "linkCharts": true, "title": "これがタイトルです。", "params": null, "forms": { "all": {} }, "width": 1363, "height": 764 } }
謎のカンマが表示されたので対処
テーブル表示にするために、以下のようにリストからtrタグなどを含む文字列を生成しました。
const rows = people .map(({ name, ok, cls }) => { return ` <tr> <td>${name}</td> <td> <a class="${cls}">選ぶ</a> <cwdb-action action="call" endpoint="${context.invokedFunctionArn}" confirmation="本当に${name}を選びますか?"> { "person": "${name}", "go": ${ok} } </cwdb-action> </td> </td> </tr> `; // }); }) .join("");
ここで最後の.join("")
で連結し忘れると、以下の画像のようにテーブルの前にカンマが表示されてしまいます。連結を忘れないようにしましょう。
カスタムウィジェットの更新のタイミング
カスタムウィジェットの更新は、ページの読み込み時や更新ボタンを手動で押した時だけではありません。3つのタイミングで更新するかを制御できます。 更新のタイミングの制御は、カスタムウィジェット作成時または編集時に設定できます。
ダッシュボードの自動更新のボタンを押す時
カスタムウィジェットは、ダッシュボードの自動更新の時に更新するか制御できます。
ウィジェットのサイズを変更した時
カスタムウィジェットは、ウィジェットのサイズを変更した時に更新するか制御できます。
ウィジェットのサイズは、Lambdaのハンドラの第1引数event
のwidgetContext
のwidth
とheight
に入っているようです。
ダッシュボードの期間を変更した時
カスタムウィジェットは、ダッシュボードの期間を変更した時に更新するか制御できます。
ダッシュボードの期間は、Lambdaのハンドラの第1引数event
のwidgetContext.timeRange
に入っているようです。
ダッシュボードの保存を忘れずに
カスタムウィジェットを作成したらダッシュボード右上に表示されているボタンを押して保存しましょう。筆者はかれこれ5回以上押し忘れています……。
リンク集
サンプルコードのGitHubのリンクは以下です。
参考: