grafana-scenes について
@grafana/scenesは、Grafanaプラグインでデータ駆動型ページを構築する開発者向けフレームワークです。宣言的なシーンオブジェクトのツリーで、クエリ、変数、時間範囲、レイアウト、パネル、ナビゲーションを構成します。
主な機能
- 01シーンオブジェクトによるページ構成
- 02クエリ、変数、時間範囲の管理
- 03PanelBuildersによるパネル構築
- 04ドリルダウンとタブの追加
- 05クエリデータの変換
活用シーン
- 01データ駆動型プラグインページの作成
- 02Grafanaのドリルダウンビューの構築
- 03詳細ビューのタブ分け
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ grafana/ skills --skill grafana-scenesインストール後、Claude Code で/grafana-scenesを入力して読み込まれたか確認します。
詳細
@grafana/scenes フレームワーク
宣言的なシーンオブジェクトを使って、リアクティブでデータ駆動型の Grafana プラグインページを構築します。
コアコンセプト
Scenes は、オブジェクトのツリーを構成します。SceneApp → SceneAppPage → EmbeddedScene → レイアウト → パネル。各ノードはデータ($data)、変数($variables)、時間範囲($timeRange)、およびツリー内を伝播するビヘイビア($behaviors)を所有できます。
クイックスタート:新しいシーンページ
1. シーンファイルを作成する
// src/components/scenes/MyFeature/scene.tsx
import {
EmbeddedScene, SceneFlexLayout, SceneFlexItem,
SceneQueryRunner, SceneVariableSet, QueryVariable,
PanelBuilders, VariableValueSelectors, SceneControlsSpacer,
} from '@grafana/scenes';
export function getMyFeatureScene(params: { datasource: DataSourceRef }) {
const queryRunner = new SceneQueryRunner({
datasource: params.datasource,
queries: [{ refId: 'A', expr: 'up{cluster=~"$cluster"}', instant: true, format: 'table' }],
});
const panel = PanelBuilders.table()
.setData(queryRunner)
.setTitle('My Table')
.build();
return new EmbeddedScene({
$variables: new SceneVariableSet({
variables: [
new QueryVariable({
name: 'cluster',
query: 'label_values(up, cluster)',
datasource: params.datasource,
isMulti: true, includeAll: true, defaultToAll: true,
}),
],
}),
controls: [new VariableValueSelectors({}), new SceneControlsSpacer()],
body: new SceneFlexLayout({
direction: 'column',
children: [new SceneFlexItem({ body: panel })],
}),
});
}
2. ページを作成する
// src/components/scenes/MyFeature/MyFeature.tsx
import { SceneAppPage, SceneTimeRange } from '@grafana/scenes';
export function getMyFeaturePage(params) {
return new SceneAppPage({
title: 'My Feature',
url: '/a/my-plugin-id/my-feature',
routePath: 'my-feature/*',
$timeRange: new SceneTimeRange({ from: 'now-1h', to: 'now' }),
getScene: () => getMyFeatureScene(params),
drilldowns: [],
});
}
3. SceneApp に登録する
ルートシーンファイルの SceneApp の pages 配列にページを追加します。
主なパターン
ドリルダウン(クリックして移動するナビゲーション)
drilldowns: [{
routePath: ':cluster/*',
getPage: (match, parent) => new SceneAppPage({
title: decodeURIComponent(match.params.cluster),
url: `${parent.state.url}/${match.params.cluster}`,
routePath: `${match.params.cluster}/*`,
getScene: () => detailScene(decodeURIComponent(match.params.cluster)),
}),
}]
タブ(詳細ビュー内のサブページ)
SceneAppPage で getScene の代わりに tabs: [SceneAppPage, ...] を渡します。各タブは、それぞれ独自のシーンを持つ SceneAppPage です。
変換を使ったクエリ
SceneQueryRunner を SceneDataTransformer でラップして、Grafana の変換またはカスタム RxJS オペレーターを適用します。
new SceneDataTransformer({
$data: queryRunner,
transformations: [
{ id: 'organize', options: { renameByName: { 'Value #A': 'CPU' } } },
(ctx) => (source) => source.pipe(map((frames) => /* custom transform */)),
],
})
カスタムシーンオブジェクト
SceneObjectBase を拡張し、カスタムのインタラクティブ UI 用に静的な Component を定義します。
class MyWidget extends SceneObjectBase<MyWidgetState> {
static Component = ({ model }: SceneComponentProps<MyWidget>) => {
const state = model.useState();
return <div>{state.value}</div>;
};
}
テーブル列のオーバーライド
ドリルダウンリンク、フィルタリング、単位、幅、カスタムセル用の ConfigOverrideRule オブジェクトを構築します。
パネルの種類
PanelBuilders.table()、.timeseries()、.stat()、.gauge()、.barchart() を使います。.setData()、.setTitle()、.setUnit()、.setOption()、.setOverrides() をチェーンし、最後に .build() を呼び出します。
よくある落とし穴
- ドリルダウンまたはタブがあるページでは、必ず
routePath: 'path/*'(ワイルドカード付き)を使用します - URL パラメーターには
encodeURIComponent/decodeURIComponentを使用します。K8s 名には/が含まれる場合があります - クエリ内で
$varNameとして参照する変数は、祖先のSceneVariableSetに存在する必要があります getSceneは遅延呼び出しされます。ファクトリー内で副作用を発生させないでください- インスタントクエリでは、
instant: trueとformat: 'table'の両方を設定します