この記事は、Groove Xアドベントカレンダー2022 12日目の記事です。
はじめに
こんばんは、GROOVE X の junya です。
更新がとても遅くなってしまってゴメンナサイ。
今回は、社内の slack で活用している grafana プレビュー機能についてご紹介します。
slack に grafana のリンクを共有すると、こんな感じで自動でプレビュー画像をつけてくれる仕組みです。
grafana とは?
オープンソースのデータ可視化ツールで、 BigQuery, Prometheus, InfluxDB, Spreadsheet など様々なデータソースに対応しています。
GROOVE X では主に、 Victoria Metrics で管理された機体のメトリクスの可視化に活用しています。
サービスの雰囲気は、公式のデモサイトがあるのでそちらで体験してみて下さい。
play.grafana.org
slack で grafana をプレビューする仕組み
以下のフローで、 slack に投稿された grafana の URL にプレビュー画像を自動補完しています。
- grafana の URL の投稿を slack の link_shared イベント として検知する
- シェアされた grafana の URL を元に、画像取得用の URL を生成し、画像を取得する
- slack のファイル管理に画像をアップロードする
- slack の unfurl 機能 を用いて、リンクにプレビュー画像を付与する
以下、詳しく説明していきます。
link_shared イベントの検知
slack アプリ作成時に Event Subscriptions の設定で link_shared
イベントを購読すると、アプリでリンク共有のイベントを取得できます。
以下のように、 slack-go を用いて HTTP リクエストを Slack のイベント型に変換して利用します。
package service
import (
"io"
"log"
"net/http"
"github.com/slack-go/slack/slackevents"
)
func HandleLinkSharedEvent(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
defer r.Body.Close()
evt, err := slackevents.ParseEvent(body, slackevents.OptionVerifyToken(&slackevents.TokenComparator{VerificationToken: SlackVerificationToken}))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
switch evt.Type {
case slackevents.CallbackEvent:
innerEvent := evt.InnerEvent
switch ev := innerEvent.Data.(type) {
case *slackevents.LinkSharedEvent:
return
default:
log.Printf("unhandled inner event: %v", ev)
}
default:
log.Printf("unhandled event: %v", evt)
}
}
grafana には Grafana Image Renderer というプラグインがあり、このプラグインが有効になっていれば、ダッシュボードのプレビュー画像を取得することができます。パスにある /d/
を /render/d-solo/
に変換し、 viewPanel
引数を panelId
引数に変えれば、画像を取得できるので、 Slack アプリの中では共有された URL を以下のように画像URLに変換して、画像を取得しています。
const BASEURL = "https://grafana.example.com"
func BuildImageURL(url string) string {
if strings.HasPrefix(url, BASEURL+"/d/") {
if strings.Contains(url, "viewPanel=") {
url = strings.Replace(url, BASEURL+"/d/", BASEURL+"/render/d-solo/", 1)
url = strings.Replace(url, "viewPanel=", "panelId=", 1)
} else {
url = strings.Replace(url, BASEURL+"/d/", BASEURL+"/render/d/", 1)
}
url += "&tz=Asia%2FTokyo"
return url
}
return ""
}
例えば、公式のデモサイトにある下記のダッシュボードは、
これを以下のような URL に変換すると画像を取得できます。
slack への画像アップロード
http client を用いて grafana から画像を取得したら、 slack の files.upload API を用いて、画像を slack へアップロードします。
slackClient := slack.New(SlackToken)
...
img := GetImageFromGrafana(imageURL)
reader := bytes.NewBuffer(img)
remoteFile, err := slackClient.AddRemoteFile(slack.RemoteFileParameters{
ExternalID: "some id",
ExternalURL: grafanaURL,
Title: "some title",
Filetype: "link",
PreviewImageReader: reader,
})
なお RemoteFile の機能は slack-go に無かったので、PR を出して機能を追加してもらいました。
grafana へのリンクにプレビュー画像を付与する
最後に slack の unfulring の仕組みを呼び出して完成です。
func (s *Service) handleLinkSharedEvent(ev *slackevents.LinkSharedEvent) {
...
blocks := make([]slack.Block, 0, 2)
if extraContent != "" {
blocks = append(blocks, slack.NewSectionBlock(slack.NewTextBlockObject("mrkdwn", extraContent, false, false), nil, nil))
}
blocks = append(blocks, slack.NewFileBlock("", remoteFile.ExternalID, "remote"))
_, _, _, err = slackClient.UnfurlMessage(
ev.Channel,
ev.MessageTimeStamp.String(),
map[string]slack.Attachment{
link.URL: {
Blocks: slack.Blocks{
BlockSet: blocks,
},
},
},
)
}
実際の運用では、プレビューに画像だけでなく、メトリクスに関連する付属情報も付与して、見る人にわかりやすくしています。
slack アプリのデプロイ
slack のアプリは cloud function にデプロイしています。bot の管理に serverless の仕組みは便利ですね。
Enterprise 版 Slack でのトラブル
この夏に社内の Slack を Enterprise へ移行したのですが、その際になぜかこの grafana プレビューの仕組みが動かなくなりました。
サポートに確認したところ、 Pro 版と Enterprise 版でファイル管理の仕様が異なり、
- Pro 版: アップロードした画像は、unfurl でそのまま使える
- Enterprise 版: チャネルに一度も投稿したことの無い画像は、スレッド内の unfurl 画像として利用できない
とのことでした。ちょっと腑に落ちない仕様ではあるのですが、ワークアラウンドとして、
- 画像投稿用の専用チャネルに画像を投稿してから、
- unfurl 画像として利用する
という方法で問題を回避しました。
さいごに
仕事って、ちょっとした工夫で楽しくなるし、業務効率も上がるなと思います。
GROOVE Xは、楽しくコードを書ける環境なので、ご興味のある方は是非ご連絡下さい!
recruit.jobcan.jp