📊

MattermostにRedashのチャートを貼り付ける

2021/06/19に公開

はじめに

Redash でアクセス数を可視化してるのですが、わざわざ Redash を開くのが面倒になってきたので Mattermost から Redash のチャートを取得できるkaakaa/matter-redashというものを作りました。

Slack だと Redash 連携は色々あるのに Mattermost では見当たらない = 作るしかない。
re:dash に slack bot 機能が追加されたよ! - Qiita
hakobera/redashbot: Slack Bot for re:dash

どんなもの?

Mattermost の Slash Command として動きます。

matter-redash.gif

どうやって動かす?

kaakaa/matter-redash: Redash integrations for Mattermostの README に書いています。

どういう風に動いてる?

  • Mattermost の Slash Command でmatter-redashへリクエスト飛ばす
  • 引数で指定されたチャート画像のスクリーンショットを取得
  • Mattermost へアップロード&チャートを投稿

という流れです。

以下、いくつかつまづいたところ。

Redash

Redash からチャート画像を取得するために/embed API を使用しています。(Redash の API Spec はどこかに無いだろうか・・)
redash/embed.py at 1ef2238d65c0c87ed0bc74141cca44b7766b8453 · getredash/redash

この API を叩くと SVG 入りの HTML が取得でき、そのままでは Mattermost でレンダリングできないのでGoogleChrome/puppeteerを使ってスクリーンショットを撮り、その画像ファイルを Mattermost にアップロードしています。

app/mattermost.js#L35
const embedUrl = util.format('%s://%s/embed/query/%d/visualization/%d?api_key=%s', protocol, redashHost, queryId, visualizationId, config.redash.apiKey);
console.log('Redash Embed URL: %s', embedUrl); // eslint-disable-line no-console |
const file = await webshot(embedUrl); |
app/webshot.js#L4
const webshot = async (url) => {
    const tmpFile = tempfile('.png');
    const browser = await puppeteer.launch();
    const page = await browser.newPage();

    await page.goto(url);
    await page.screenshot({path: tmpFile});
    await browser.close();

    return tmpFile;
};

そこら辺の実装方法についてはhakobera/redashbotを参考にさせていただきました。

Mattermost

Personal Access Token

Mattermost に API 経由で画像ファイルをアップロードするために投稿権限が必要で、今の所Personal Access Tokenのみサポートしてます。
なので、Mattermost は Personal Access Token が実装されたv4.1.2以上である必要があります。

Mattermost 上でチャート画像を見やすくするため、一旦チャート画像をメッセージの添付ファイルとして投稿し、[ファイルに対する公開リンク](Sharing Files — Mattermost 4.5 documentation)を取得し、Message Attachmentsとして投稿するようにしています。

app/mattermost.js#L39
    // Upload webshot image file
    const imageFormData = new FormData();
    imageFormData.append('files', fs.createReadStream(file));
    imageFormData.append('channel_id', req.body.channel_id);

    const uploadFile = await client.uploadFile(imageFormData, imageFormData.getBoundary());

    // Get public link of uploaded file
    const fileId = uploadFile.file_infos[0].id;
    const post = await client.createPost({
        channel_id: req.body.channel_id,
        message: 'This message is posted solely to get the public link and should be deleted immediately.',
        file_ids: [fileId]
    });
    const link = await client.getFilePublicLink(fileId);
    console.log('Mattermost Public Link: %s', link.link); // eslint-disable-line no-console

    // Response to Mattermost
    res.header({'content-type': 'application/json'});
    res.send(commandResponse(url, link.link));

    // Delete unnecessary post.
    client.deletePost(post.id);

最後に不要な Post を削除していますが、これは Mattermost の仕様上、一度 Post に対する添付ファイルとして投稿しないとファイルの公開リンクを取得できないためです。
なので、実行ごとに(このメッセージは削除されました)という投稿が発生していまうので少し気持ち悪いです。

注意点

Personal Access Token も Public Link も設定で無効にできたりするので、システムコンソールから有効にしておく必要があります。

おわりに

とりあえず運用してみて、使えそうならShare your Mattermost projects | Mattermostから Mattermost チームに報告する所存。

あと、Mattermost プラグインとして作り直したいな。

Discussion