Inside of LOVOT

GROOVE X 技術ブログ

grafana をプレビューする slack アプリの話

この記事は、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 機能 を用いて、リンクにプレビュー画像を付与する

以下、詳しく説明していきます。

slack アプリ作成時に Event Subscriptions の設定で link_shared イベントを購読すると、アプリでリンク共有のイベントを取得できます。

slackアプリの Event Subscriptions 設定

以下のように、 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:
            // handle link shared event here.
            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=") {
            // grafana can render panel itself with /d-solo/ and "panelId" parameter
            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)
        }
        // add timezone parameter to show metrics with JST
        url += "&tz=Asia%2FTokyo"
        return url
    }
    return ""
}

例えば、公式のデモサイトにある下記のダッシュボードは、

これを以下のような URL に変換すると画像を取得できます。

slack への画像アップロード

http client を用いて grafana から画像を取得したら、 slack の files.upload API を用いて、画像を slack へアップロードします。

slackClient := slack.New(SlackToken)

...

// 画像URLを元に画像のバイナリを取得する
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