跳到主要内容

核心概念

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

场景

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

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() 订阅对象的状态将使组件对状态更改具有响应性。对场景对象状态的每一次更改都是不可变的,并将导致组件重新渲染。

修改状态

要更改场景对象的状态,请使用每个场景对象都具有的 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 包装实例。这可以确保对象的原始父对象不被更改,同时允许您在另一个场景对象的状态中存储对该实例的引用。

源代码

查看示例源代码