metabase-data-app-setup について
Metabase Data App の開発者向けセットアップガイドです。付属テンプレートを使い、リモート同期する Git リポジトリに Vite、React、TypeScript のアプリを作成します。構成やサンドボックスでのプレビュー、ビルド方法を扱います。
主な機能
- 01付属アプリテンプレートのコピー
- 02リモート同期リポジトリ内での配置
- 03サンドボックスでのプレビュー
- 04単一 JS バンドルのビルド
- 05開発診断情報の確認
活用シーン
- 01Metabase リポジトリで Data App を開始
- 02新規アプリのディレクトリとマニフェストを設定
- 03Data App のプレビューとビルド
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ metabase/ metabase --skill metabase-data-app-setupインストール後、Claude Code で/metabase-data-app-setupを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
.gitignoretemplate/.env.local.example.gitignoredata_app.yamlLICENSEpackage.jsonREADME.mdtsconfig.jsonvite.config.tstemplate/actions/README.mdtemplate/queries/README.mdtemplate/src/App.tsxindex.tsxtheme.ts詳細
Metabase データアプリを作成する
Metabase のデータアプリとは、単一の JS バンドルであり、ホストが Near Membrane サンドボックス内で読み込み、独自の React ツリー内でレンダリングします。スキャフォールドは Vite + React + TypeScript プロジェクトです。src/ 配下のソース、実際の Metabase に対してアプリをプレビューする開発サーバー (本番環境で Metabase が使用するのと同じ Near Membrane サンドボックス + ディストーションルールを通すため、npm run dev は、アプリがバンドルするサードパーティライブラリも含めて本番環境と同じように動作します)、そして単一の dist/index.js を生成する npm run build を備えています。(サンドボックスはビルド済みバンドルを実行するため、変更すると再ビルドされ、ソフトリロード が行われます — サンドボックス内でバンドルを再評価し、アプリを再マウントしますが、認証/SDK はロードされたままです — モジュールのホットスワップではなく; コンポーネントの状態はリセットされますが、ブラウザ全体のリフレッシュは発生しません。) 開発プレビューでは、隅に ⚠ 診断 ツールバーも表示され、ランタイムエラーをキャプチャします — これには、サンドボックスのそれ以外では不透明なブロックされた API のメッセージも含まれます — そのため、失敗が握りつぶされることなく表面化します。同じデータは http://localhost:5174/__data-app/diagnostics で JSON として提供され、これがあなたがそれを読む方法です (以下の「診断フィードを読む」を参照) — あなたにはブラウザではなくシェルがあり、これらの失敗はそうでなければターミナルからは見えません。
データアプリは Git から提供されます (また、/api/apps を通じて作成および更新することもできます)。単一のリポジトリがリモート同期 (管理者 → 設定 → リモート同期) を介して Metabase に接続されます。各アプリはそのリポジトリ内の独自のディレクトリ data_apps/<slug>/ に存在します — そのソース、data_app.yaml (slug/名前/パス/コレクション)、そして data_app.yaml が宣言する path (デフォルトでは dist/index.js) にコミットされたビルド済みバンドルです。そのコレクションは、保存された質問と、それが使用するメトリクスおよびアクションのコピーとともに、data-apps 名前空間のすべてのコレクションと同様に、リポジトリの collections/data_apps/ 配下の YAML として存在します。リモート同期のインポートごとに、Metabase は各アプリを実体化し、/apps/<slug> で提供します。ここで slug は data_app.yaml が宣言する slug です。したがって、このスキルは常に接続されたリポジトリの data_apps/<slug>/ ディレクトリ内にスキャフォールドし、スタンドアロンプロジェクトとして決してスキャフォールドしません。
スキャフォールドはこのスキル内の ./template/ に同梱されています — スキルとともにインストールされた Vite + React + TypeScript プロジェクトです。ステップ 3 では単にそれをアプリディレクトリにコピーするだけです; その後、スキルはカスタマイズ + 最初のアプリコンテンツのステップを案内します — プロジェクトファイルをゼロから生成することは決してありません。package.json、vite.config.ts、tsconfig.json、または src/index.tsx を手書きしていることに気づいたら、やめてください — 代わりにテンプレートをコピーしてください。
このスキルを呼び出すタイミング
- 「新しいデータアプリをスキャフォールドする」 / 「Metabase データアプリを作成する」 / 「データアプリプロジェクトをセットアップする」
- 「データアプリを構築したい」 / データアプリを作成するというあいまいな意図
- データアプリバンドルを生成する新しいエージェントタスクを開始するとき。
- 「 データアプリを削除する」 / 「データアプリを削除する」 — 以下のアプリの削除のみが該当します。
- 既存のデータアプリプロジェクトに対して、画面を構築する、Metabase データを使用する、スキーマファイルを生成または更新する、保存された質問 / テーブル / メトリクス / アクションを接続する、フィルターを追加する、またはデータフックを作成するタスクでは、このスキルを使用しないでください。これらは既存のデータアプリの編集タスクとして扱い、ユーザーの言い回しからエージェントの通常のスキルディスカバリーフローを使用してください。
ステップ 1 — リモート同期リポジトリを見つける
データアプリは、リモート同期を介して Metabase に接続された Git リポジトリ内に存在します。スキャフォールドする前にそれを見つけてください:
- 尋ねる: 「この Metabase にリモート同期を介して接続された Git リポジトリはすでにありますか?」
- はい → そのローカルパスを尋ねます。
- いいえ → ユーザーに作成 (管理者 → 設定 → リモート同期で接続するプレーンな Git リポジトリ) してそのパスを共有するよう依頼します。スキルはリポジトリを作成または接続しません — それはユーザーが所有します。
- パスが存在し、Git 作業ツリーである (
.gitがある) ことを確認します。このリポジトリが、以下のすべてのステップの作業ディレクトリです。
ステップ 2 — アプリに名前を付けて、そのディレクトリを作成する
- スキャフォールディングの前に、アプリの slug —
/apps/<slug>という URL — を確定してください。そのため ダッシュ区切りでなければなりません: 小文字の英字、数字、および単一のダッシュ ([a-z0-9]+(?:-[a-z0-9]+)*)、たとえばsales-overviewです。それ以外 (大文字、スペース、アンダースコア) は同期時に拒否されます。目的がまだ明確でない場合は、「このアプリは何のためですか?」と一行で尋ね、slug を提案してください。そして確認を取ってください。 <repo>/data_apps/が存在することを確認してください。存在しない場合は作成してください。<repo>/data_apps/<slug>/を作成してください。すでに存在する場合は、既存のプロジェクトとして扱ってください (以下を参照) — 確認なしに絶対に上書きしないでください。
既存のアプリの検出
<repo>/data_apps/<slug>/ にすでにプロジェクトが入っている場合は、それが現在の data-app-template と一致するか確認してください。以下を すべて チェックしてください:
vite.config.tsが一行であること:export default dataAppConfig()(@metabase/embedding-sdk-react/data-app-dev/configから)。ローカルのconfig/ディレクトリは 存在しません: バンドル契約全体 (externals/globals、開発サンドボックスのエントリー、CSS/SVG の処理) は、スキャフォールドではなくその SDK 設定の中にあります。dataAppConfigは厳選された一連のオーバーライド (現在はportのみ) だけを受け取ります。契約プラグインは常に適用され、オーバーライドすることはできません。src/index.tsxがDataAppFactory(@metabase/embedding-sdk-react/data-appの型) をデフォルトエクスポートし、{ component, providerProps? }を返していること (引数なし)。data_app.yamlが、このスキルのtemplate/data_app.yamlと同じversion:を宣言していること (この行のないマニフェストはバージョン 1 です)。より低いバージョンはドリフトではなく、古いアプリです: 停止してください。 その移行は別のタスクです。古いデータアプリを拡張する前に移行するには、エージェントの通常のスキル検出フローを使用してください。
すべてのチェックに合格 → テンプレートに準拠しています。「このアプリを拡張しますか、それとも別の slug で新しいものをスキャフォールドしますか?」と尋ねてください。拡張する場合 → コピーステップをスキップし、src/ を編集してください。新規の場合 → 別の slug を選び、ステップ 2 からやり直してください。
いずれかのチェックに失敗 → テンプレートに準拠していません (古いスキャフォールドまたはドリフト)。停止してください。 構造が現在のテンプレートと異なること、拡張するとバンドル契約が壊れるリスクがあることをユーザーに伝え、(1) 移行する (別のタスクです。古いデータアプリを移行するにはスキル検出を使用してください)、(2) 新しい slug で新規にスキャフォールドし、コードを移植する、(3) 自己責任でそのまま進める、のいずれを行うか尋ねてください。回答を待ってください。
明示的な確認なしに既存のファイルを絶対に上書きしないでください。
ステップ 3 — テンプレートをアプリディレクトリにコピーする
テンプレートは このスキルの中 の ./template/ に同梱されています (skills add metabase/metabase/skills#release-x.<major>.x を介してスキルと一緒にインストールされます)。それをアプリディレクトリにコピーしてください:
APP_DIR="<repo>/data_apps/<slug>"
# `<skill-dir>` = the directory this SKILL.md was loaded from
# (e.g. `.claude/skills/metabase-data-app-setup`); the template is its `template/` subfolder.
cp -R "<skill-dir>/template/." "$APP_DIR/"
データアプリはリモート同期リポジトリの サブディレクトリ であり、それ自体が独立したリポジトリではありません — したがってこれは単なるコピーであり、ネストされた git clone / git init では決してありません。以下のすべては $APP_DIR 内で 実行されます。
コピーにはルートレベルの 2 つのディレクトリ queries/ と actions/ が含まれており、それぞれ README.md のみを保持しています。空のままであっても両方を保持してください: アプリが実行するすべてのクエリは queries/ 内の defineQuery(...) エクスポートであり、すべてのアクションは actions/ 内の defineAction(...) エクスポートであり、フックはコンパイル時にそれ以外を拒否します。最初の useMetabaseQuery / useMetabaseQueryObject / useAction 呼び出しを書く前に、両方の README を読んでください。
ステップ 4 — カスタマイズ
テンプレートが <repo>/data_apps/<slug>/ に入ったら (以下のすべてをそのディレクトリから実行してください):
-
package.jsonのnameを slug に一致するように編集してください。 -
@metabase/embedding-sdk-reactを公開されている data-apps タグに固定してください (テンプレートは*で同梱されています):npm install @metabase/embedding-sdk-react@64-alphaこれは、
@metabase/embedding-sdk-react/data-appエントリポイント(アプリの API)を備えた現在の内部テスト用 SDK ビルドと、vite.config.tsが使用する@metabase/embedding-sdk-react/data-app-dev/configエントリポイント(サンドボックスエントリを提供する dev/build プリセット)に解決されます。データアプリはまだリリースされていないため、latestや64-stableは使用しないでください。 -
リポジトリルートの
.gitignoreが.env.localを無視することを確認する — これは認証情報ファイルを作成する前に行い、シークレットがコミットされることが決してないようにしてください。リポジトリに.gitignoreがない場合は作成し、エントリがなければ追加します。ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" if [ -z "$ROOT" ]; then echo "MISSING (run this from inside the connected git repo)" else GITIGNORE="$ROOT/.gitignore" # Create the repo-root .gitignore if absent, then ensure `.env.local` is # ignored so the credentials file (next step) can never be committed. [ -f "$GITIGNORE" ] || : > "$GITIGNORE" grep -qxF ".env.local" "$GITIGNORE" || echo ".env.local" >> "$GITIGNORE" fi -
Metabase の認証情報をリポジトリルートに設定します —
<repo>/.env.local(通常はアプリディレクトリから 2 階層上)、アプリディレクトリではありません。そこにある 1 つの.env.localがリポジトリ内のすべてのデータアプリに使われます。存在しない場合はサンプルから作成し、ファイルを出力せずに 2 つの変数が設定されていることを確認します(他のシークレットが含まれている可能性があります) —
sourceして、pass/fail のシグナルのみを echo し、値は決して出力しないでください。# Resolve the repo root first; an unguarded $(git ...) would expand to # "/.env.local" outside a repo and touch a system-level file. ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" if [ -z "$ROOT" ]; then echo "MISSING (run this from inside the connected git repo)" else ENV_FILE="$ROOT/.env.local" [ -f "$ENV_FILE" ] || cp .env.local.example "$ENV_FILE" # Source inside a subshell so the vars never leak into your environment. ( source "$ENV_FILE" 2>/dev/null [ -n "$DATA_APP_MB_URL" ] && [ "$DATA_APP_MB_URL" != "mb_replace_me" ] && [ -n "$DATA_APP_MB_API_KEY" ] && [ "$DATA_APP_MB_API_KEY" != "mb_replace_me" ] ) && echo "creds present" || echo "MISSING" fiMISSINGと表示された場合は、ユーザー自身に<repo>/.env.localのDATA_APP_MB_URL(実行中の Metabase インスタンス)とDATA_APP_MB_API_KEY(Admin → Authentication → API keys、型付きスキーマに応答できるのは管理者のみであるため Administrators グループのキー)を記入するよう依頼してください — キーが必要になる前の、最初の段階で。ユーザーに API キーをチャットに貼り付けるよう決して依頼せず、
.env.localやその変数をcat/echo/ 出力しないでください。 これは git で無視されており、他のシークレットが含まれている可能性があります — ファイルの内容とキーは会話やあなたのコンテキストに入ってはなりません。キーを必要とするすべてのコマンドは(上記のように)ファイルをsourceするため、シェルが値を直接使用します。あなたが見るのはcreds present/MISSINGのシグナルのみで、シークレット自体を見ることは決してありません。(creds presentは両方の変数が入力されており、デフォルトのmb_replace_meプレースホルダーではないことのみを意味します — URL やキーが有効であることを意味するものではありません。不正なキーは後でリクエストが失敗したときに明らかになります。) -
npm install(またはユーザーが好むパッケージマネージャー — テンプレートにはロックファイルが同梱されていないため、npm/yarn/pnpm/bunのいずれも動作します。クローン後に既存のロックファイルが現れた場合はそれを使用してください)。 -
ロックファイルとビルド済みバンドルの両方をコミットする必要があります。 Metabase は
data_app.yamlで宣言されたpath(テンプレートはdist/index.jsにビルドされます)にあるファイルを、コミットされた Git ツリーから直接配信し、ロックファイルはインストールの再現性を保ちます。npm installとビルドの後にgit statusで確認してください: 両方ともコミット可能なファイルとして表示される必要があります。 -
npm run devを実行し、http://localhost:5174 のプレビューでスターターの「Hello, data app」メッセージが表示されることを確認します。 -
プレビューで CORS が発生する場合は、Admin → Embedding → Embedded analytics SDK → CORS の下に
http://localhost:5174を追加します。 -
data_app.yamlを記入します — テンプレートのマニフェストで、現在はアプリディレクトリにあります。これは Metabase が同期時に読み取るアプリごとの設定で、アプリごとに 1 ファイルです。そのフィールド、必須のもの、およびそのルールは、Metabase representation format 仕様の Data App セクションで定義されています。Metabase representation YAML の読み書き用のスキルを読み込み(スキルディスカバリーを使用)、その仕様に従ってください。そのようなスキルが利用できない場合は、npx skills add metabase/agent-skills/skills --skill metabase-representation-formatでインストールしてください。それでも読み込めない場合は、停止してユーザーに伝えてください。このアプリのname、slug、descriptionを設定し、entity_idをnpx representations generate-entity-idで生成した ID に設定します(npm install後に、アプリ自身の dev 依存関係から)。versionとserdes/metaはテンプレートが同梱するままにし、pathもビルド出力を変更しない限りそのままにします。collectionは項目 10 まで空のままにします。他のアプリのマニフェストから値をコピーしないでください。description— 任意: アプリが何をするかを示す 1 つの短い文です。管理 UI ではアプリ名の下に表示されるため、管理者は一目でアプリを見分けられます。同期では、空白はすべて単一のスペースにまとめられ、255 文字を超えるものは拒否されます。管理一覧では、残った内容が切り捨てられずに折り返されるため、そこでは 1 文が読みやすく表示され、段落では周囲の行が圧迫されます。テンプレートのプレースホルダーは、この アプリについての実際の文に置き換えるか、名前以外に何も付け加えない場合はその行を完全に削除してください。version— このアプリのコードが対象とするデータアプリ契約のバージョンで、整数です。テンプレートには、このスキルが対象とするバージョンが同梱されています。手動で変更しないでください。Metabase が提供するバージョンを更新するのは、契約に破壊的変更があった場合だけです。古いバージョンのアプリは、管理一覧で Outdated と表示され、他のすべてのユーザーからは非表示になり、現在の契約に移行してversionを一致するように引き上げ、再ビルドして同期するまで開けなくなります。この移行は、1 バージョンずつ行う、別途指示された手順です。スキルディスカバリーを通じて実行し、決して場当たり的に行わないでください。allowed_hosts— アプリがfetch/XHRで 外部 API を直接呼び出す場合にのみ必要です。サンドボックスはデフォルトですべてのネットワーク送信をブロックします。ここにオリジン(完全一致または*.サブドメインワイルドカード)を記載すると、npm run dev(開発サーバーの CSP)と Metabase(iframe の CSP + サンドボックス)の両方でそれが開かれます。Metabase インスタンスを 記載しないでください — Metabase のデータはuseMetabaseQueryデータフックで読み取り、useActionで書き込みます(認証は SDK が処理します)。生のfetchは決して使用しません。アプリが Metabase とのみ通信する場合は、allowed_hostsを完全に省略してください。ネイティブの
<form action="…">送信、および<iframe src="…">/ナビゲーションは、同じ許可リストに従います。preventDefaultしてuseAction/fetchで書き込むクライアントサイドの<form onSubmit>を優先してください。ネイティブ送信は、サンドボックス化された iframe をアプリから 離れてナビゲートさせます。どうしても使用する場合は、対象ホストがallowed_hostsに含まれていないとブロックされます(送信にはform-action、埋め込み/ナビゲーションにはframe-src)。ナビゲートまたは埋め込みを行うホストは、フレーム表示も許可している必要があります(X-Frame-Options/frame-ancestors)— 多くの公開サイトは許可していません。 -
アプリのコレクションを作成します。
npx representations generate-entity-idでエンティティ ID を生成し、リポジトリのcollections/data_apps/の下にコレクションのファイルを書き込み(アプリのコレクションのファイルの書き方については、スキルディスカバリーを使ってデータアプリのガイダンスを見つけてください)、その ID をdata_app.yamlのcollectionに設定します。
アプリのファイルの順序
アプリの YAML はこの順序で書き込まれ、各ステップにはその前のステップが必要です:
data_app.yaml。ステップ 3 でテンプレートからコピーし、項目 9 で記入します。collections/data_apps/の下のコレクションのファイル。項目 10 でdata_app.yamlにcollectionとして名前が指定されます。npm run print-resources。アプリがqueries/またはactions/に定義を持ったら実行します。保存された質問を、data_app.yamlが指定するコレクションに出力するため、マニフェストがコレクションを指定するまで実行を拒否します。- コレクションのディレクトリ内の残りのファイル: クエリごとの保存された質問と、それらが使用するメトリクスおよびアクションのコピーで、出力された内容から書き込みます。
スキャフォールディングは 2 番目で終了します。最後の 2 つはアプリのデータレイヤーの構築の一部です。それにはスキルディスカバリーを使用してください。
ステップ 5 — スターターアプリを検証する
この時点でデータアプリは存在します。このワークフローは、プロジェクトのスキャフォールドを作成し、スターターバンドルが動作することを確認することに焦点を当てたままにしてください。
-
npm run typecheckを実行します。 -
npm run check-resourcesを実行します。これはdata_app.yamlがコレクションを指定していない場合、またはファイルのないコレクションを指定している場合に失敗し、リポジトリの YAML をフォーマットに対して検証します。Metabase 内の何も変更しません。 -
npm run buildを実行します。 -
git statusに、アプリのソース、ロックファイル、data_app.yaml、collections/data_apps/配下のコレクションのファイル、およびビルド済みバンドル(デフォルトではdist/index.js)がコミット可能なファイルとして表示されることを確認します。 -
ユーザーがライブプレビューを求めていた場合は、
npm run devを実行し、スターターの「Hello, data app」画面がサンドボックスプレビュー経由でレンダリングされることを確認します。 -
プレビューを開いた状態で、診断フィードを一度確認します — ランタイムの失敗があなたに表示される唯一の場所です(診断フィードを読む を参照):
curl -s "http://localhost:5174/__data-app/diagnostics?startEventId=0"clients: 1であり、"alert": trueのエントリがないことを期待します。clients: 0はプレビュータブが開かれていないことを意味するため、空のフィードは何も証明しません。
ユーザーがデータアプリの作成、スキャフォールド、またはセットアップのみを求めた場合は、ここで停止してください。
次のタスクが実際のアプリ UI の構築または反復である場合 — 特に既存のデータアプリ、Metabase のデータ、生成されたスキーマファイル、保存された質問、テーブル、メトリクス、アクション、フィルター、セマンティックレイヤーのエンティティ、またはデータフックに言及している場合 — それは別個の既存データアプリの編集タスクとして扱ってください。それらの用語から、エージェントの通常のスキルディスカバリーフローを使用してください。このスキャフォールドスキルをデータレイヤーの作成ルールで拡張しないでください。
変更が本当に必要な場合を除き、src/index.tsx や tsconfig.json を変更しないでください。 ビルド/開発のセットアップ全体は dataAppConfig() の背後にある SDK 内にあります(これは開発用 HTML シェルも提供します — 編集すべき index.html はありません)。そのため vite.config.ts は単に次のとおりです:
import { dataAppConfig } from "@metabase/embedding-sdk-react/data-app-dev/config";
export default dataAppConfig();
dataAppConfig が公開するのは厳選されたオーバーライドのセットのみです(現在は port だけ)。コントラクト全体 — ファクトリの形、externals/globals、開発サンドボックスのエントリ、CSS のインライン化、SVG をコンポーネントとして扱うサポート — は組み込まれており、オーバーライドできません。これは意図的なもので、データアプリが Metabase の読み込むものから逸脱しないようにするためです。触るべきローカルビルド設定はありません(また index.html もありません — SDK の開発サーバーが HTML シェルを提供します)。そして src/index.tsx への調整は依然としてファクトリの形を壊し、ドリルポップアップやルーティングを静かに壊すリスクがあります。
追加の Vite プラグイン、エイリアス、または define のための逃げ道は意図的にありません — port が唯一の調整項目です。もっと必要だと思うなら、ほぼ確実に必要ありません。代わりに src/ 内で解決してください。
意味のある編集のラウンドごとに、npm run typecheck を実行してください。 これは src/ と vite.config.ts に対して tsc --noEmit を実行します — SDK の型に対する誤った prop の形、壊れたリファクタリング、不足しているインポートなどを検出します。Vite 開発サーバーは型チェックをしません(トランスパイルのみ)。そのため、本番 CI 実行で失敗するようなエラーが、成功している npm run dev セッションの中に目に見えずに潜んでいることがあります。タスクを完了と宣言する前に実行してください。
引き渡しの前に、パッケージの衛生状態を再確認してください。 @metabase/embedding-sdk-react は、対象環境に期待されるデータアプリ SDK のソース/タグを使用するべきです。アプリが日付範囲のみを必要とする場合、日付ピッカーの依存関係をインストールするべきではありません — react-datepicker、react-day-picker、flatpickr、または UI スイートのピッカー(@mui/x-date-pickers、antd、rsuite など)ではなく、@metabase/embedding-sdk-react/data-app の DateRangePopover を使用します。選択した react-datepicker のバージョンが実際に必要としない限り、@types/react-datepicker をインストールするべきではありません。
診断フィードの読み方
npm run dev はツールバーが表示するすべての内容を JSON として配信します。これが、あなたがランタイム障害を確認する唯一の方法です。サンドボックスのブロック、CSP による拒否、失敗したクエリ、キャッチされなかったエラーは、ターミナルにも npm run typecheck にも到達しないからです。コードを読んで確認できない変更を行った後は、必ずこれをチェックしてください。
ループ: 編集前に nextEventId をメモする → 変更を行う(自動的に再ビルドされます)→ 再度読み取る。startEventId は範囲に含まれ、ページのリロード後も保持されます。
curl -s "http://localhost:5174/__data-app/diagnostics?startEventId=0"
{
"entries": [{
"eventId": 31, "kind": "blocked-network", "alert": true,
"summary": "Blocked fetch to api.example.com (not in allowed_hosts)",
"detail": null, // stack frames, when any
"hint": "Add https://api.example.com to allowed_hosts in data_app.yaml …",
"buildId": 7 // the bundle generation that reported it
}],
"clients": 1, // connected preview tabs — 0 means nothing ran
"buildId": 7, // the generation running now
"staleEntries": 164, // held back, reported by an older build — see below
"nextEventId": 32 // pass back as ?startEventId=
}
clients: 0 は健全を意味しません — これはプレビュータブが開かれていないことを意味するため、空の entries は何も証明しません。まず http://localhost:5174 を開いてください。
フィードは、実行中のバンドルについて答えるものであり、これまでに起こったすべてについて答えるものではありません。 保存のたびにアプリが再ビルドされ再マウントされるため、複数ステップの編集は、コンパイルできない、あるいはレンダリングできないビルドを経由して実行されます。それらからのエラーは、プレビューがすでに置き換えたコードを説明するものです。それらは保留され、staleEntries にカウントされます。編集中に読み取れば、現在のビルドの障害が見えるのであり、その山積みが見えるわけではありません。何も失われません。再ビルドはアプリを最初から再実行するため、まだ壊れているものは新しい buildId の下で再び自身を報告します。また ?includeStale=true は、ビルドの比較が目的である場合に保留されたエントリを返します。失敗したビルドは buildId を進めません — プレビューは最後の正常なバンドルを実行し続けるため、そのエントリは最新のままです。
kind ごとにトリアージしてください(常に hint を読んでください — それは正確な修正方法を示しています。報告する際に summary をやわらげないでください):
kind | 修正 |
|---|---|
blocked-network / csp-violation | data_app.yaml の allowed_hosts にオリジンを追加し、npm run dev を再起動します(許可リストと CSP は起動時に読み込まれます) |
blocked-api | 本番環境でもブロックされます — SDK API を使うか、その呼び出しを削除してください。回避策を講じないでください |
sdk-call + alert: true | Metabase リクエストが失敗しました。クエリを修正してください — summary にエンドポイントとステータスがあります |
error | 実際のバグです。detail にスタックがあります |
テキストは切り詰められ、バッファは最後の 200 イベントを保持します。curl -X DELETE .../__data-app/diagnostics は、すべての読み取り者に対してバッファをクリアします。
ソースの規約
1. TSX を書く
プレーンな ESM + TSX、通常のパッケージインポート:
// src/components/CustomerCard.tsx
import { StaticQuestion } from "@metabase/embedding-sdk-react";
type Customer = { name: string; questionId: number };
export default function CustomerCard({ customer }: { customer: Customer }) {
return (
<article>
<h3>{customer.name}</h3>
<StaticQuestion questionId={customer.questionId} height={300} width="100%" />
</article>
);
}
2. 最初から構造を意識する
スターターアプリを拡張した後のデフォルトのプロジェクトレイアウト:
queries/ (root level, beside package.json — NOT under src/)
│ └── orders.query.ts (defineQuery exports; one file per topic)
actions/ (root level, beside package.json — NOT under src/)
│ └── orders.action.ts (defineAction exports; one file per topic)
src/
├── index.tsx (template — the factory; don't edit)
├── App.tsx (routing + composition only)
├── theme.ts
├── metabase.data.ts (generated schema — use skill discovery for the data-app semantic layer)
├── pages/ (one file per screen)
│ ├── Overview.tsx
│ └── CustomerDetail.tsx
├── components/ (shared UI)
│ └── Card.tsx
├── hooks/ (custom hooks that wrap a query export, never the query itself)
│ └── useCustomers.ts
├── lib/ (pure helpers / derivations)
│ └── format.ts
└── types/ (shared TS types)
└── customer.ts
Vite は src/index.tsx から到達可能なすべてを単一の dist/index.js IIFE にバンドルします。src/ のレイアウトはあくまで可読性のためのものです。しかし queries/ と actions/ はそうではありません。これら 2 つのルートレベルのディレクトリにある各定義は、リポジトリの collections/data_apps/ 配下にあるアプリのコレクション内のファイルによって裏付けられており、それらが一致するまで npm run build は失敗します。また、クエリとアクションのフックはそこで宣言された defineQuery / defineAction エクスポートのみを受け付けます。フック呼び出しの箇所、src/ 配下、または定義からのスプレッドで書かれたクエリオブジェクトはコンパイルできません(Property 'definedWithDefineQuery' is missing)。修正方法は、それを queries/ に移動してインポートすることであり、キャストで済ませてはいけません。
アプリに複数のタブ(またはトップレベルの画面切り替え)がある場合、初期ロード時にデフォルトの — 最も左 / 最初の — タブが選択されていなければなりません。 アプリは空白ページ、空のシェル、またはユーザーがクリックするのを待つ「何も選択されていない」状態で起動してはいけません。エージェントはこれを繰り返し忘れます。ローカル状態のタブの場合、最初のレンダリングでそれが表示されるように、アクティブなタブを最初のタブに初期化してください:
const [active, setActive] = useState(TABS[0].id); // default = leftmost tab
タブが代わりに URL ルート(複数ページ)で裏付けられている場合も、ルーターを介して同じルールが適用されます — データアプリのルーティングにはスキルディスカバリーを使用して、ベースパス / がデフォルトタブに解決されるようにしてください。いずれの場合も、アプリを新規にロードして確認してください。最も左のタブの内容がすぐに表示され、選択されているように見えるはずです。
ビルド出力は 1 つの自己完結した .js ファイルのみで、それ以外は何もありません。 バックエンドは単一のバンドルを提供するため、サイドカーファイルはありません。CSS は JS にインライン化され、インポートされたすべてのアセット(画像、フォント、URL としての SVG)は data URI として base64 でインライン化されます。したがって import logo from "./logo.png" / import iconUrl from "./icon.svg" はすぐに使える data URI 文字列を返し、SVG は ?react サフィックス(組み込みの svgr)を使って React コンポーネントとしてインポートすることもできます: import Icon from "./icon.svg?react"。すべてが dist/index.js に焼き込まれます — 大きなバイナリは避けてください。インライン化するとバンドルが肥大化するからです。(エディターが ?react インポートを認識しない場合は、src/ 内の .d.ts に declare module "*.svg?react"; を追加してください。)
3. SDK の値は正しい SDK エントリポイントからインポートする
ビルドは React、JSX ランタイム、@metabase/embedding-sdk-react、および @metabase/embedding-sdk-react/data-app を、本番環境と npm run dev の両方でサンドボックスのグローバルに外部化します — dev では、サンドボックスのエントリが npm パッケージからそれらを与えるため、バンドルは両方で同じように動作します。通常どおりエントリポイントからインポートするだけです:
// ✅ correct
import { StaticQuestion } from "@metabase/embedding-sdk-react";
import {
DataAppRouter,
DataAppLink,
useMetabaseQuery,
} from "@metabase/embedding-sdk-react/data-app";
// ❌ wrong — no globalThis pattern; you'd be reading nothing
const { MetabaseProvider, StaticQuestion } = globalThis;
App.tsx で <MetabaseProvider> をレンダリングしないでください。 dev エントリ(SDK の dev プリセットによって提供される)と本番ホストの両方が、それぞれ独自のレルムに存在するプロバイダーでツリーをラップします — バンドル内でラップすると、SDK のリスナー経由の setState パスが Near Membrane サンドボックスを経由することになり、ドリルポップアップ、プラグインの初期化などがサイレントに壊れます。
4. react も通常どおりインポートする
ビルドは react と react-dom を外部化するため、通常のインポートは両方のモードで同じように解決されます — 本番ホストと dev サンドボックスの両方が、それらをサンドボックスのグローバルとして与えます:
import { useState, useEffect, useMemo } from "react";
TSX ファイルでは import React from "react" は不要です — テンプレートは自動 JSX ランタイムを使用しています(tsconfig.json の jsx: "react-jsx"、dataAppConfig() には React プラグインが含まれています)。コンパイラーは必要な JSX ランタイムのインポートを注入します(本番では react/jsx-runtime、dev では react/jsx-dev-runtime — どちらも外部化され、サンドボックスによって与えられます)。JSX と名前付きインポートを書くだけです — それだけです。
React の 型(例: ComponentType、ReactNode、RefObject)には、React. 名前空間ではなく名前付き型インポートを使用してください:
import type { ComponentType, ReactNode } from "react";
テーマのルール
MetabaseProvider の theme( src/theme.ts で定義)は、SDK コンポーネントの外観を変更する唯一の方法です。これはバンドル自体の外観用のスタイルシートではありません。
| フィールド | 目的 | 備考 |
|---|---|---|
colors.brand | SDK ウィジェットのアクセント | データアプリのプライマリカラーを使用してください。 |
colors.brand-hover、colors.brand-hover-light | SDK ウィジェットのホバー/アクセント背景 | ホバー時のテキストとコントラストがつく、控えめなサーフェスの色合いを使用してください。brand と同じ彩度の高い色は使わないでください。 |
colors.charts | チャートのパレット | 文字列の配列です。鮮やかなブランド系の色のみを使用してください。淡い色合いは決して使わないでください — パレットのインデックス 1 以降はラベルの描画に使われることがあり、白背景に淡い色は見えなくなります。 |
colors.positive / colors.negative | 意味を示すインジケーター | |
colors.background、colors.background-secondary | SDK コンポーネントのサーフェス | SDK コンポーネントの直接の親コンテナと一致させる必要があります。 チャートが白いカードの中にある場合は "white" を使用してください。色付きのページ上にある場合は、その色合いを使用してください。 |
colors.text-primary、text-secondary、text-tertiary | SDK サーフェス上のテキスト | background とコントラストをつける必要があります。 白背景の場合:#1f2937 などの暗い色を使用してください。SDK のデフォルトの text-primary はほぼ白に解決されるため、白いサーフェス上で未設定のままにすると白いテキストが見えなくなります。オーバーライドする際は常に background と text-primary をペアで設定してください。 |
fontFamily | SDK ウィジェットのタイポグラフィ |
ホバー色はコントラストのペアです。colors.text-hover を設定する場合は、
開いたメニュー、チャートタイプのセレクター、可視化設定のドロップダウンにおいて、
colors.brand-hover / colors.brand-hover-light とコントラストがついていることを確認してください。青い brand の場合、
#EAF4FF のような淡いホバーサーフェスを使用し、ブランドブルーそのものは使わないでください。
テーマは SDK ウィジェットのみをスタイルし、あなた自身の UI には決して適用されません。 ページの背景、ヘッダー、カードのラッパーなどには、インラインの style={{ background: "#f5f5f7" }} か、自身の要素に対する CSS モジュールを使用してください。
サブツリーごとのテーミングは期待しないでください。 ページ上に複数の <MetabaseProvider> があると、単一の CSS 変数のスロットを奪い合います。状態に応じて見た目を変える必要がある場合は、App レベルで sdkTheme を再計算して再レンダリングしてください — ツリー全体が再テーマ化されます。
カスタムエラー UI(任意)
埋め込まれた質問やダッシュボードが読み込めない場合 — questionId が削除された、ユーザーにアクセス権がない、またはリクエストが失敗した — ホストはそのコンポーネントの代わりにニュートラルな組み込みのエラー状態(くすんだアイコン + SDK のメッセージ)をレンダリングします。これにはセットアップは不要です。アプリにそれを再スタイルする強い理由がない限り、そのままにしておいてください。
アプリの見た目に合わせるには、ファクトリーの providerProps(theme を保持するのと同じオブジェクト)で独自の errorComponent を返します。これは通常の providerProps のキーであり、ファクトリーの形を変更するものではありません — 安全に追加できます:
// src/components/AppError.tsx
import type { SdkErrorComponentProps } from "@metabase/embedding-sdk-react";
export default function AppError({ message }: SdkErrorComponentProps) {
return (
<div style={{ padding: 16, textAlign: "center", color: "#6b7280" }}>
{message}
</div>
);
}
// src/index.tsx — add the key alongside `theme`
import AppError from "./components/AppError";
const factory: DataAppFactory = () => ({
component: App,
providerProps: { theme: sdkTheme, errorComponent: AppError },
});
ルール:
- これはアプリ内のすべての SDK コンポーネントのエラー UI を置き換えます — not-found だけではありません — そのため、コピーは汎用的に保ってください。どのエラーが発生したかの想定ではなく、
message(または独自の文言)をレンダリングしてください。 messageはReactNodeです — そのままレンダリングしてください。.toString()を呼び出したり、インデックスアクセスしたりしないでください。- 小さなプレゼンテーショナルコンポーネントに保ってください。これはホストのプロバイダー内でレンダリングされますが、リーフ UI として扱ってください — データフックや
useMetabaseQueryは使わないでください。 - ニュートラルなデフォルトを保つには、
errorComponentを完全に省略してください。再実装する必要はありません。
利用可能な SDK サーフェス
バンドルは React フック/JSX、@metabase/embedding-sdk-react の SDK コンポーネント、および @metabase/embedding-sdk-react/data-app のデータアプリ固有のルーティング/クエリ API をインポートします。dataAppConfig() はビルド時にこれらのパッケージを外部化するため、本番環境では実行時にホスト側のコピー(SDK パッケージの場合は globalThis.__metabase_sdk__ / globalThis.__metabase_data_app__)を参照し、Vite 開発サンドボックスはインストールされた npm パッケージから同じグローバルを付与します。
<MetabaseProvider> はバンドルの App.tsx ではレンダリングされません — 開発用エントリとホストが、それぞれのモードに応じてラップします。バンドルの作成者は以下のコンテンツのみをレンダリングします。
| インポート | 目的 |
|---|---|
React("react" から) | フック(useState、useEffect など)、JSX ランタイム。react: "React" を介してホスト側の React に外部化されます。 |
StaticQuestion | ドリル不可の質問。プロパティには questionId、card、withChartTypeSelector、height、width が含まれます。 |
InteractiveQuestion | ドリル可能な質問。StaticQuestion と同じプロパティに加えてドリル動作を備えます。SDK がアドホックにレンダリングする質問には card={{ query }} を使用します。両者の選択方法や visualization / visualizationSettings については、チャートのレンダリング: Metabase ファーストを参照してください。 |
MetabaseCard | @metabase/embedding-sdk-react からの型専用インポートで、visualization または visualizationSettings を使用する SDK がアドホックにレンダリングするカード向けです。データレイヤーのコードを作成する前に、生成クエリカードの完全なコントラクトについてはスキルディスカバリを使用してください。 |
CreateQuestion、MetabotQuestion | その他の質問バリアント。 |
StaticDashboard、InteractiveDashboard、EditableDashboard | ダッシュボードのバリアント。 |
CreateDashboardModal | 新規ダッシュボードフロー用のモーダル。 |
CollectionBrowser | コレクションのピッカー。 |
@metabase/embedding-sdk-react/data-app のエクスポート | ルーティング、スキーマに基づくデータ読み取り、アクション、クリップボード、サンドボックスセーフな統合のためのデータアプリ専用ヘルパー。スキーマに基づくクエリ、生成されたスキーマファイル、フィルター、メトリクス、アクション、その他のデータレイヤーの動作は、既存データアプリの編集作業として扱ってください。そのコードを作成する前にスキルディスカバリを使用してください。 |
DateRangePopover、DateRangeCalendar、useDateFormatter(@metabase/embedding-sdk-react/data-app から) | フィルターバー用の日付範囲選択。DateRangePopover はアプリ自身のトリガー要素をラップし、その下に Metabase スタイルの範囲カレンダーを開きます。DateRangeCalendar はそのカレンダーをインラインで表示するものです。value/onChange は YYYY-MM-DD 文字列の [start, end] ペアで、片方だけ選択されている間はその端が null になります。useDateFormatter().formatDateRange(value) は、new Date("YYYY-MM-DD") の UTC パースのバグなしに、インスタンスのロケールでトリガーのラベルを作成します。<input type="date"> やサードパーティ製ピッカーの代わりにこれらを使用してください — 依存関係なし、CSS インポートも不要です。 |
ブロックされる API
Near Membrane サンドボックスは、これらのグローバルに対して実行時に例外をスローします。代わりに提供されている代替手段を使用してください:
| ブロック対象 | 代わりに使用するもの |
|---|---|
ネットワーク — fetch、XMLHttpRequest、WebSocket | Metabase の読み取りと書き込みには SDK/データアプリの API を使用してください — 生の fetch は決して使用しないでください。生の fetch/XHR は data_app.yaml の allowed_hosts(ステップ 4)に記載された外部オリジンにのみ到達できます。それ以外(Metabase のオリジンを含む)はすべて例外をスローします。WebSocket は常にブロックされます。 |
UI ダイアログ — alert、confirm、prompt | 独自のツリー内で React モーダルをレンダリングしてください。 |
ストレージ — localStorage、sessionStorage、indexedDB、document.cookie | データアプリはリロードをまたいでステートレスであるものとして扱い、Metabase アクションを介して永続化してください。 |
Window / history ナビゲーション — window.open、history.pushState、history.replaceState | アプリ内では useDataAppLocation().navigate を、外部では <a target="_blank" rel="noopener"> を使用してください。 |
クリップボード — document.execCommand("copy")、navigator.clipboard | copy(書き込み専用): import { copy } from "@metabase/embedding-sdk-react/data-app" を実行し、ユーザーイベントから await copy(text) を呼び出します。読み取り/貼り付け API は存在しません — それが存在すると、バンドルがユーザーのコピーした内容を外部に持ち出せてしまうためです。 |
その他の navigator.* デバイス API — geolocation など | 利用できません。 |
入力/クリップボードイベント用のグローバルな document/window リスナー — keydown、keyup、keypress、beforeinput、input、paste、copy、cut、before*paste/copy/cut、compositionstart/update/end、storage | リスナーを独自の要素にアタッチするか、特定の入力/コンテナで React ハンドラー(onKeyDown、onPaste など)を使用してください。スクリプトが所有する要素に同じリスナーをアタッチしても引き続き機能します。 |
経験則: window.X、document.X、navigator.X、history.X、または任意のストレージグローバルに触れようとしている場合は、いったん立ち止まり、上記の提供されている代替手段を選んでください。提供されているサーフェス(React + React DOM + SDK コンポーネント + データフック + useAction + DataAppRouter + DateRangePopover + copy)は、あらゆる日常的なニーズをカバーしています。それ以外のものは意図的に到達不可能になっています。
チャートのレンダリング: まず Metabase
これは行単位、セクション単位、アプリ単位で一度に決めるものではなく、要素ごとに下す判断です。カスタムコードを必要とするタイルが 1 つあれば、その行にはカスタムタイルが 1 つあることになり、行全体がカスタムタイルになるわけではありません。残りは SDK コンポーネントのままで、書式設定、テーマ、ドリルスルーを維持します。高さの統一やカードの外観は、各タイルが配置されるコンテナからもたらされるため、「他のタイルもそれに合わせるべきだ」というのは、他のタイルを手作業で作る理由にはなりません。
組み込みのビジュアライゼーションは、インスタンスのテーマ、アクセシビリティ、ツールチップ、書式設定、ドリルスルーをそのまま備えています。React で作ったグラフはそれらを一切備えておらず、どちらかが変更されればすぐにアプリ内の他のすべてのグラフから乖離していきます。したがって、問いは決して「どちらが自分のデザインに近い見た目か」ではなく、Metabase はこれを表示できるか? です。
- できる →
useMetabaseQueryObject+StaticQuestion/InteractiveQuestion。 棒、折れ線、面、複合、行、トレンド、円/ドーナツ、スカラー/スマートスカラー、ゲージ、プログレス、ファネル、散布図、ウォーターフォール、ボックスプロット、サンキー、ピボット、マップ、オブジェクト/リストビュー、並べ替え可能なテーブル、およびグラフ種別リストにあるその他すべて。また、ビジュアライゼーション設定で表現を担える場合 — 軸、ラベル、積み上げ、目標、トレンドライン、分割パネル、系列、書式設定、テーブル/円/ピボット/リストの設定 — や、ユーザーが並べ替え、列の検査、ドリルスルー、ダウンロードから恩恵を受ける場合も同様です。生成されたスキーマオブジェクトからセマンティッククエリを構築し、返されたqueryを分割代入し、その値だけをカードオブジェクトに渡します —<StaticQuestion card={{ query }} visualization="pie" ... />。{ query, error, isLoading }フックの結果全体をcard.queryとして渡さないでください。 - できない →
useMetabaseQueryと自前のコンポーネント。 ユーザーがカスタムビジュアライゼーションを求めた場合、または Metabase が描画するどの方法でもリクエストを表現できない場合に限ります。特注のスコアカード、アラートパネル、ナラティブなレイアウトや混在コンテンツのカード、そのグラフ/テーブルの外観では表現できないカスタムインタラクション、カレンダーグリッド、タイムライン、ヒートストリップ、ラジアルビュー、カスタムマップ、ドメイン固有の図など通常とは異なる形式、および複数のクエリを 1 つの視覚単位に組み合わせる要素です。行の処理は型付けを保ってください。アプリがすでに持っているチャート用依存関係を使用し、なければ SVG で問題ありません。
StaticQuestion は 2 つの中で既定です。 ビジュアライゼーションだけを描画し、それ以外は何も描画しません — クエリバーもエディターも保存もなく、withChartTypeSelector、withDownloads、title、およびサイズ指定の props を受け取ります。要素が探索されることを意図している場合 — ドリルスルー、フィルタリング、グラフ種別の切り替え、ノートブックエディター — は InteractiveQuestion を使用します。オフにしない限り保存ボタンが表示されます — isSaveEnabled の既定値は true です — これは閲覧者にコレクションへの保存フローを提供するもので、アプリではなく Metabase に属するため、アプリが意図的に編集サーフェスである場合を除き isSaveEnabled={false} を渡してください。これは既定のレイアウトに適用されます。InteractiveQuestion に独自の子を与えるとレイアウトが置き換わるため、<InteractiveQuestion.QuestionVisualization /> を組み合わせた場合はツールバーも保存ボタンも描画されません。
すでに見出しを持つカードやセクションに question が配置される場合は title={false} を渡してください。 InteractiveQuestion の既定のレイアウトは question 自身のタイトルを表示するため、2 つ表示されることになります。StaticQuestion はすでにそれを非表示にしています。
「私たちのスタイリングに合わせる必要がある」というのは、手作業で作る理由にはなりません。グラフ種別には visualization を、設定レベルの変更には visualizationSettings を渡し、それ以外は SDK にテーマを適用します。データアプリ内の円グラフは Metabase の円グラフです — ユーザーがカスタムのものを求めた場合を除きますが、それはデザインリファレンス、ブランドカラー、あるいは特注品のスクリーンショットから推測するものではなく、ユーザー自身が下す判断です。
単一値の KPI はスカラーです — またはスマートスカラー、ゲージ、プログレスです。タイルが数値に加えて Metabase が描画しないもの、たとえば星の行、キャプション、2 つ目のクエリからの合計を求めている場合は、数値にはスカラーを描画し、追加分をその横に置きます。数値自体が単一のクエリから取得できない場合にのみ、手作業で作ってください。
isLoading が true の間は常にスピナー(またはスケルトン)を表示する — 空のスロットや古い値は決して表示しないでください。データが到着したときにレイアウトシフトが発生します。リフトされた / 派生したクエリ(isLoading を下に渡す)や useAction の isExecuting(ボタン内のスピナー + disabled={isExecuting})にも同じルールが適用されます。
フックのコントラクト自体 — ジェネリクス、テーブルソース、セグメント、メジャー、ブレイクアウト、ソート、デバッグ — については、スキーマに基づくデータレイヤーのコードを作成する前にスキルの検出を使用してください。
アプリのレイアウト — フレームの高さを埋める
Metabase と npm run dev はどちらもバンドルにフルハイトのフレームを与えますが、それとルート要素の間で高さを設定するものがないため、フレームより短いアプリはコンテンツが終わる場所で終わり、その背景、サイドバー、フッターが裸の白の上に短い帯として残ります。
そのため、アプリの最も外側の要素には minHeight: "100vh"(パディングがある場合は boxSizing: "border-box" も)を設定し、ページの背景を描画します。height ではなく minHeight を使用してください — より高いコンテンツは引き続きスクロールできる必要があります。最下部まで到達すべき領域(サイドバー、固定フッター)は、flex: 1 を持つフレックスカラムのルートに配置します。最も空の画面 — ローディング、空の状態、単独の KPI — を確認してください。これらが短くレンダリングされるものです。
SDK コンポーネントのサイズ指定
SDK コンポーネントは親に自動でフィットしません — height/width を指定しないと、固有のサイズでレンダリングされ、はみ出します。外側のコンテナやカードの高さだけを設定しても不十分です。高さは、ビジュアライゼーションを所有する SDK コンポーネントに設定します:
- チャートのみ:
InteractiveQuestion.QuestionVisualizationにheightを渡します。 - クエリバー付きのデフォルトの質問レイアウト:
InteractiveQuestionにheightを渡します。 - 静的質問:
StaticQuestionにheightを渡します。
チャートが利用できる実際のボディの高さを使用してください。たとえば、カードの高さが 560px で 60px のヘッダーがある場合、height="500px" を渡します。
<div style={{ height: 360 }}>
<StaticQuestion
questionId={1}
height="100%"
width="100%"
withChartTypeSelector={false}
/>
</div>
InteractiveQuestion や StaticQuestion を、ホバー時にクリップしたり移動したりするコンテナでラップしないでください。埋め込まれた Metabase UI の周囲では overflow: hidden、ホバートランスフォーム、ホバーによるレイアウトシフトを避けてください。ポップオーバー、メニュー、チャートのツールチップには安定したジオメトリと可視のオーバーフローが必要です。
Metabase への同期
データアプリは Git によって配信されます — アプリディレクトリをコミットすると、Metabase が次回のリモート同期インポートでそれをプルします。
queries/またはactions/を変更した後は、collections/data_apps/配下にあるアプリのコレクションのファイルを一致するように更新し(定義が使用するものをsrc/metabase.data.tsがカバーしていない場合は最初に再生成します)、npm run check-resourcesを実行します。ステップ 4 のプルが適用するまで、何も Metabase に到達しません。npm run buildはdata_app.yamlのpathにバンドルを生成します(テンプレートはdist/index.jsにビルドします)。アプリのコレクションファイルが定義と一致しない場合は失敗します。それらを修正して再ビルドしてください。- リポジトリのルートから、アプリディレクトリ — その
data_app.yaml、ビルドされたバンドル(pathが指すファイル)、ソース、ロックファイル — を、アプリのコレクションファイルと一緒にコミットし、プッシュします:git add data_apps/<slug> collections/data_apps git commit -m "Add <slug> data app" git push - アプリは次回のリモート同期インポート — 手動の Pull changes(Admin > Data apps / Remote sync)、自動インポートのポーリング、または再起動 — で Metabase に表示され、
/apps/<slug>でアクセスできます。
アプリを「デプロイ」することを提案したり、バンドルがステージング環境にどのように到達するかを尋ねたりしないでください — 別途のデプロイステップは存在せず、その質問はユーザーを混乱させるだけです。Metabase は接続されたリポジトリからコミットされたバンドルを次回の同期で直接インポートします。変更が Metabase が同期するブランチ上にあれば — ユーザーがどのようにそこへ持っていくとしても(マージされた PR、またはそのブランチへの直接プッシュ) — それをプルして Metabase の
/apps/<slug>でアプリを開くように伝えるだけです。
- 更新するには: 定義が変更された場合はアプリのコレクションファイルを更新し、新しいビルドをコミットして、再度プルします。
- 削除するには: 以下の アプリの削除 を参照してください。
アプリの削除
ユーザーがデータアプリの削除を求めた場合、リポジトリのルートから、そのディレクトリおよびそのコレクションのファイルを 1 つのコミットで削除し、プッシュします:
git rm -r data_apps/<slug> collections/data_apps/<collection>.yaml collections/data_apps/<collection>
git commit -m "Remove <slug> data app"
git push
<collection> は、data_app.yaml が collection として指定するコレクションです: collections/data_apps/ 配下にある、entity_id がその値であるファイルと、その隣にある同名のディレクトリ(Data App: Order Desk の場合は data_app__order_desk)を指します。次回のプルでアプリが削除され、それに伴いそのコレクション、保存された質問、メトリクスのコピー、アクションのコピーも削除されます。
コレクションのファイルを伴わずにディレクトリを削除した場合でも、次回のプルでアプリは削除され、それに伴いコレクションも削除されます。その後、Metabase からの次回のプッシュでそれらのファイルがリポジトリから削除されます。いずれにしても両方を削除してください: そのプッシュまでに、すべてのファイルを再読み込みするプルによって、どのアプリも所有しないコレクションとして復元されてしまい、その後のプッシュでそれが保持されてしまいます。逆のケースは拒否されます: コレクションのファイルが消えているのにディレクトリが残っているアプリは、プルに失敗します。
よくある落とし穴
| 症状 | 修正方法 |
|---|---|
| 「Failed to fetch the user, the session might be invalid.」 | API キーが不正か CORS です — `( ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"; [ -n "$ROOT" ] && source "$ROOT/.env.local" 2>/dev/null; [ -n "$DATA_APP_MB_URL" ] && [ "$DATA_APP_MB_URL" != "mb_replace_me" ] && [ -n "$DATA_APP_MB_API_KEY" ] && [ "$DATA_APP_MB_API_KEY" != "mb_replace_me" ] && curl -H "x-api-key: $DATA_APP_MB_API_KEY" "$DATA_APP_MB_URL/api/user/current" |
| グラフのラベルが表示されない。 | テーマで text-primary を設定してください(テーマのルールを参照)。 |
| グラフがインスタンスの他の部分と見た目が違う、テーマを無視する、ツールチップ・書式設定・ドリルスルーがない。 | それは React で作られています。代わりに StaticQuestion/InteractiveQuestion と visualization でレンダリングしてください(グラフのレンダリング: Metabase を優先を参照)。 |
| グラフがコンテナからはみ出す。 | SDK コンポーネントに height / width を渡してください(SDK コンポーネントのサイズ指定を参照)。 |
| アプリの背景が途中で途切れ、短いコンテンツの下が真っ白になる。 | ルートに minHeight: 100vh を指定してください(アプリのレイアウトを参照)。 |
| 実行時に「Invalid hook call」。 | React が 2 つあります。dataAppConfig() は react を外部化します — react/react-dom がインストールされていること、2 つ目の React やバージョンの不一致した React を追加していないことを確認してください。 |
| バンドルが数 MB になる。 | React と SDK は契約プラグインによって外部化されるはずです — vite.config.ts が依然として dataAppConfig() を使用していること、および固定された data-apps SDK タグがインストールされていることを確認してください。(数 MB には満たないものの大きめのバンドルは、アセットがインライン化されている場合もあります — 上記の単一ファイルに関する注意を参照してください。) |
dist/index.js が __dataAppFactory__ に代入しない。 | src/index.tsx は DataAppFactory を export default する必要があります — プリセットがそれを IIFE グローバルに組み込みます。 |
Cannot find module '@metabase/embedding-sdk-react'。 | npm install(またはお使いのパッケージマネージャーでの同等のコマンド)を実行してください。型はパッケージから直接提供されます。 |
フック呼び出しで Property 'definedWithDefineQuery' is missing / Property 'definedWithDefineAction' is missing。 | そのフックが、定義ではなくインラインオブジェクト、satisfies 型のオブジェクト、またはスプレッドコピーを受け取りました。ルートレベルの queries/ から defineQuery で(または actions/ から defineAction で)エクスポートし、そのインポートを渡してください。キャストせず、呼び出し箇所でオブジェクトを defineQuery(...) でラップしないでください。それはコンパイルは通りますが、保存されたクエスチョンに裏付けられることはありません。 |
| ドリルポップアップが開かない / SDK コンポーネントが空になる / 開発時に実行時エラー「MetabaseProvider not found」。 | App.tsx が独自の <MetabaseProvider> をレンダリングしています — それを削除してください。開発エントリ(SDK)と本番ホストがプロバイダーを提供します。バンドル内でそれをラップすると、SDK の状態パスがサンドボックスを経由して壊れます。 |
開発プレビューが空白 / Bundle did not assign a function to __dataAppFactory__ / 開発時のサンドボックスエラー。 | src/index.tsx がファクトリをデフォルトエクスポートしていないか、サンドボックスがバンドルを評価する際にアプリのコードが例外を投げています。診断フィード(curl -s "http://localhost:5174/__data-app/diagnostics?startEventId=0")または開発ツールバーの Diagnostics パネルから実際のエラーを確認してください。 |
ネットワーク呼び出しが npm run dev では動作するが、Metabase に同期した後はブロックされる。 | それは決して許可されていません — 開発サーバーを再起動せずに allowed_hosts を編集したため、実行中のサンドボックスと CSP は依然として起動時のリストを使用しています。npm run dev を再起動してください。フィードの manifest セクションがこれを restartRequired として示します。 |
| SDK を再ビルドまたは再インストールしたのに、プレビューが依然として古いバージョンのように動作する。 | Vite は SDK を node_modules/.vite に事前バンドルし、そのキャッシュをパッケージのバージョンでキー付けするため、その場での再ビルドではキャッシュが無効になりません。開発サーバーを停止し、node_modules/.vite ディレクトリを削除して、再度起動してください(または npx vite --force を実行してください)。 |
| 編集のたびにコンポーネントの状態がリセットされる。 | 想定どおりの動作です。開発時はバンドルを再ビルドし、ソフトリロード を行います — サンドボックスで再評価してアプリを再マウントします(認証/SDK は読み込まれたまま、ブラウザのリフレッシュはなし)。アプリは分離されたレルムで評価されるバンドルであるため、モジュールレベルの HMR / Fast Refresh はなく、ローカルのコンポーネント状態はリセットされます。 |
| 本番で URL は変わるが UI が更新されない(開発では動作する)。 | ルーティングプリミティブを @metabase/embedding-sdk-react/data-app からインポートし(メインエントリではなく)、引き続き dataAppConfig() を使用してください — これは /data-app を外部化するため、そのルーティングはインライン化されません(インライン化すると Near-Membrane を経由した React の状態バッチ処理のバグが発生します)。 |