跳至主要内容

核心概念

本主题解释了场景的核心概念以及如何在创建自己的场景时使用它们。

场景

场景是称为场景对象的对象集合。这些对象代表场景的不同方面:数据、时间范围、变量、布局和可视化。场景对象共同构成一个对象树

Scene objects tree

场景允许您对对象进行分组和嵌套。数据、时间范围或变量等内容可以添加到树中的任何对象,使其对该对象及其所有后代对象可用。因此,场景允许您创建具有多个时间范围的仪表板、可以共享和转换的查询或嵌套变量。

@grafana/scenes 带有多个对象SceneQueryRunnerSceneFlexLayoutVizPanel用于解决常见问题。但是,您也可以创建自己的场景对象以扩展功能。

场景对象

场景由称为场景对象的原子对象构建。场景对象使用以下内容定义:

  • 状态 - 扩展 SceneObjectState 的接口。
import { SceneObjectState } from '@grafana/scenes';

// 1. Create interface that describes state of the scene object
interface CounterState extends SceneObjectState {
count: number;
}
  • 模型 - 扩展 SceneObjectBase 类的类。模型包含场景对象的逻辑。
import { SceneObjectBase } from '@grafana/scenes';

export class Counter extends SceneObjectBase<CounterState> {
public static Component = CounterRenderer;

public constructor() {
super({
count: 0,
});
}

public onIncrement = () => {
this.setState({
count: this.state.count + 1,
});
};
}
  • React 组件 - 用于渲染场景对象。
import React from 'react';
import { SceneComponentProps } from '@grafana/scenes';

function CounterRenderer({ model }: SceneComponentProps<Counter>) {
const { count } = model.useState();

return (
<div>
<div>Counter: {count}</div>
<button onClick={model.onIncrement}>Increase</button>
</div>
);
}

状态

场景对象可以具有关联的状态。对象的形状通过一个接口来表达,该接口必须扩展 SceneObjectState 接口

interface CounterState extends SceneObjectState {
count: number;
}

订阅状态更改

组件可以通过使用在渲染时接收的 model 属性来读取场景对象的状态。要订阅状态更改,请调用 model.useState 方法

function CounterRenderer({ model }: SceneComponentProps<Counter>) {
const { count } = model.useState();

// ...
}

使用 model.useState() 订阅对象的 state 将使组件对 state 更改具有反应性。对场景对象 state 的每次更改都是不可变的,并且会导致组件重新渲染。

修改状态

要更改场景对象的状态,请使用每个场景对象都具有的 setState 方法。这可以直接从组件中完成

function CounterRenderer({ model }: SceneComponentProps<Counter>) {
const { count } = model.useState();
const onIncrement = () => model.setState({ count: count + 1 });

// ...
}

这也可以从场景对象类中完成

export class Counter extends SceneObjectBase<CounterState> {
// ...
public onIncrement = () => {
this.setState({
count: this.state.count + 1,
});
};
}

function CounterRenderer({ model }: SceneComponentProps<Counter>) {
const { count } = model.useState();

return (
<div>
<div>Counter: {count}</div>
<button onClick={model.onIncrement}>Increase</button>
</div>
);
}
注意

我们建议您在场景对象中而不是在组件中实现修改状态的方法,以将模型复杂性与组件分离。

数据和时间范围

使用 $data 属性将来自 Grafana 数据源的数据添加到场景。使用 SceneQueryRunner 场景对象配置查询

import { SceneQueryRunner } from '@grafana/scenes';

const queryRunner = new SceneQueryRunner({
datasource: {
type: 'prometheus',
uid: '<PROVIDE_GRAFANA_DS_UID>',
},
queries: [
{
refId: 'A',
expr: 'rate(prometheus_http_requests_total{}[5m])',
},
],
});
注意

您的 Grafana 实例必须配置了指定的数据源。

为了使 SceneQueryRunner 工作,您必须在场景中添加一个时间范围。每个场景对象都有一个 $timeRange 属性,可以将 SceneTimeRange 场景对象添加到其中。要为上例中创建的查询运行程序指定时间范围,请在传递给构造函数的对象中添加 $timeRange 属性

import { SceneQueryRunner, SceneTimeRange } from '@grafana/scenes';

const queryRunner = new SceneQueryRunner({
datasource: {
type: 'prometheus',
uid: '<PROVIDE_GRAFANA_DS_UID>',
},
queries: [
{
refId: 'A',
expr: 'rate(prometheus_http_requests_total{}[5m])',
},
],
$timeRange: new SceneTimeRange({ from: 'now-5m', to: 'now' }),
});

将创建的 queryRunner 添加到您的场景中。现在,场景中的每个对象都可以访问提供的数据

const scene = new EmbeddedScene({
$data: queryRunner,
body: ...
})

每个场景对象都有一个 $data$timeRange 属性,可以对其进行配置。由于场景是对象树,因此通过 SceneQueryRunnerSceneTimeRange 分别配置的数据和时间范围对它们添加到的对象及其所有后代对象可用。

在下面的示例中,每个 VizPanel 使用不同的数据。“面板 A”使用在 EmbeddedScene 上定义的数据,而“面板 B”有自己的数据和时间范围配置

// Scene data, used by Panel A
const queryRunner1 = new SceneQueryRunner({
datasource: {
type: 'prometheus',
uid: '<PROVIDE_GRAFANA_DS_UID>',
},
queries: [
{
refId: 'A',
expr: 'rate(prometheus_http_requests_total{}[5m])',
},
],
});

// Panel B data
const queryRunner2 = new SceneQueryRunner({
datasource: {
type: 'prometheus',
uid: '<PROVIDE_GRAFANA_DS_UID>',
},
queries: [
{
refId: 'A',
expr: 'avg by (job, instance, mode) (rate(node_cpu_seconds_total[5m]))',
},
],
});

const scene = new EmbeddedScene({
$data: queryRunner1,
// Global time range. queryRunner1 will use this time range.
$timeRange: new SceneTimeRange({ from: 'now-5m', to: 'now' }),
body: new SceneFlexLayout({
direction: 'row',
children: [
new SceneFlexItem({
width: '50%',
height: 300,
body: PanelBuilders.timeseries().setTitle('Panel using global time range').build(),
}),
new SceneFlexItem({
width: '50%',
height: 300,
body: PanelBuilders.timeseries()
.setTitle('Panel using local time range')
// Time range defined on VizPanel object. queryRunner2 will use this time range.
.setTimeRange(new SceneTimeRange({ from: 'now-6h', to: 'now' }))
.setData(queryRunner2)
.build(),
}),
],
}),
});

对 SceneObject 的引用和父级引用

非常重要的是,您不要在多个不同的场景中或同一个场景中的多个位置重复使用同一个场景对象的实例。场景对象的父级在它成为另一个场景对象状态的一部分时自动设置。因此,如果您想将同一个场景对象实例用作多个场景对象状态的一部分,您有两个选择。

  • 克隆源场景对象。这将创建一个与源对象没有关联的单独实例。
  • 使用 SceneObjectRef 来包装实例。这确保了对象的原始父级不会更改,同时允许您将对该实例的引用存储在另一个场景对象的 state 中。

源代码

查看示例源代码