🐡

metabaseのprojectに、自動でcardを追加する

4 min read

例えば「100社以上の顧客がいて、各社のDAUを取得したい」といった場合、手動で追加するのは大変なのでプログラム的に自動追加する方法を模索しました。結果、世界のどこを探しても情報が無かったので自作しました。

1: card を作る
2: project の中でカードが存在しない座標を計測する。
3: 2 で計測した座標の中から望ましい位置に card を post する

という三段階が必要です。またいくつか注意と恐らくバグがあるので、その注意点もまとめています。

前準備

前もって以下の項目だけは最低限用意した方がいいです。

  • 一旦作りたいカードを一つ手動で作る
  • あなたのmetabaseのURL/api/card/:id で情報を見て、それをコピーする
  • 使いたい NativeQuery をコピーする(バックエンドが理解できる形でパースしないといけないので、もうそのままコピーした方が速い)
  • 縦幅横幅を統一する 座標を特定しやすくするため(既にカードの一部が専有している座標に post すると、上にかぶさる形でカードが生まれる)

metabase の API ドキュメントは required は記載あるものの、openapi 仕様でいうタイプのようなものは記載がなく、required のネストした中で必要な項目はドキュメントだけではわかりません。そして必要な項目がないと、内部のバックエンドエラーを起こしてエラー文がそのまま返ってきます。そのままコード見るのもいいですが get api した方が速いです。

#!/usr/bin/env bash
# example
# ./add_dau.sh password "DAU" "nekoneko company"

PASSWORD=$1
CARD_NAME=$2
TARGET_COMPANY=$3
METABASE_URL="https://nekoneko"
METABASE_USERMAIL="nekoneko@nmail.com"
METABASE_SESSION=$(curl -X POST \
  -H "Content-Type: application/json" \
  -d "{\"username\": \"${METABASE_USERMAIL}\", \"password\": \"${PASSWORD}\"}" \
  "${METABASE_URL}/api/session" | jq '.id')

# 1: cardをつくる nameとdataset query パラ必須。内部情報も無いと先方のバックエンドcljのエラーが帰ってくる。以下が最小の設定項目。
# https://github.com/metabase/metabase/blob/master/docs/api-documentation.md#post-apicard
dau_card_json() {
  cat <<EOF
  {
    "name": "${CARD_NAME}",
    "display": "line",
    "visualization_settings": {
      "graph.dimensions": [
        "日付"
      ],
      "graph.metrics": [
        "DAU"
      ],
      "graph.show_trendline": true,
      "series_settings": {
        "DAU": {
          "line.missing": "zero"
        }
      }
    },
    "database_id": 99999,
    "collection_id": 11111,
    "query_type": "native",
    "dataset_query": {
      "type": "native",
      "native": {
        "query": "SELECT CAST(\"date\" AS DATE) AS \"日付\", COUNT(distinct(\"user\")) AS \"DAU\" FROM \"company\" WHERE \"company\" = \"${TARGET_COMPANY}\""
        "template-tags": {}
      },
      "database": 99999
    }
  }
EOF
}
dau_card_id=$(curl -X POST \
  -H "X-Metabase-Session: ${METABASE_SESSION}" \
  -H "Content-Type: application/json" \
  "${METABASE_URL}/api/card" \
  -d "$(dau_card_json)" | jq '.id')

[ -z "${dau_card_id}" ] && exit 1;

# 2: ボードの最も最下部の座標を特定する
# https://github.com/metabase/metabase/blob/master/docs/api-documentation.md#get-apidashboardid
lowestRow=$(curl \
  -H "X-Metabase-Session: ${METABASE_SESSION}" \
  "${METABASE_URL}/api/dashboard/追加したいダッシュボードID" | \
  jq '.ordered_cards[].row' | sort -nr | head -n1)

# 3: 作成したcardをダッシュボードにいれる
# https://github.com/metabase/metabase/blob/master/docs/api-documentation.md#post-apidashboardidcards`
col=0
row=$((lowestRow+4))
dau_card_in_project_json() {
  cat <<EOF
  {
    "cardId": ${dau_card_id},
    "parameter_mappings": [],
    "series": [],
    "sizeX": 4,
    "sizeY": 4,
    "col": ${col},
    "row": ${row}
  }
EOF
}
curl -X POST \
  -H "X-Metabase-Session: ${METABASE_SESSION}" \
  -H "Content-Type: application/json" \
  "${METABASE_URL}/api/dashboard/追加したいダッシュボードID/cards" \
  -d "$(dau_card_in_project_json)"

# ここから横にずらしつつ連続してなにか投稿するなら
# col=$((col+4))
# ここから縦にずらしつつ連続してなにか投稿するなら
# row=$((row+4))
# curl ...

最下部に追加していくスタイルではなければ the largest col を取得すれば大丈夫です。

注意

現在 metabase では card は削除できなくなってます。 そのため project から card を排除する場合、GUI 上から削除するしか無いのですが、不正な値で作成してしまったカードは削除ボタンが存在しません。


こうなってしまうとどうしようもなくなってしまいます。それもあって厳格に[ -z "${dau_card_id}" ] && exit 1;でチェックしています。この対処法については card_id が null の場合そもそもバックエンド側で弾くべきだとは思うので PR を作成しました。

https://github.com/metabase/metabase/pull/17110