为后端数据源插件添加查询迁移
随着插件的发展,插件定义的查询模型可能需要进行重大更改。原因包括插件依赖的第三方服务发生重大变化、重构、新功能等等。更改数据源查询模型时,插件应实现迁移逻辑,以将现有查询从旧格式转换为最新格式。此迁移逻辑应包含在后端(用于非前端发起的查询,例如警报)和前端(用于使查询适应新版本的 QueryEditor
)。
为何添加查询迁移处理程序
为了确保兼容性并保持流畅的性能,查询迁移处理程序会将旧查询转换为当前格式。这种方法允许您在不破坏现有查询或重复代码的情况下提供更新,为用户提供平滑的插件更新过渡体验。
开始之前
根据您在本指南中执行某个步骤所采用的方法,您可能需要满足某些先决条件。这些先决条件是
- 必须将 Grafana 配置为将数据源作为独立的 API 服务器运行,此行为受功能标志 grafanaAPIServerWithExperimentalAPIs 控制。
- 插件必须运行在 Grafana 11.4 或更高版本上。
有关这些先决条件的更多信息,请参阅步骤 5,但如果您的插件无法满足这些要求,可以使用现有 API 的替代方法。
实现后端迁移处理程序
要实现后端迁移处理程序,请在后端代码中添加迁移逻辑。本指南将引导您完成使用最新工具自动迁移查询并避免重复代码所需的步骤。
本指南中详述的迁移系统不支持双向迁移。仅涵盖正向迁移。查询迁移不会自动持久化,因此用户需要手动保存更改以确保流程按预期工作。
步骤 1(可选):添加查询模式
首先,插件不需要强类型查询。虽然这降低了插件开发的门槛,但未定义类型的插件更难扩展和维护。本指南的第一步是添加所需文件来定义插件查询。
请参阅以下示例:grafana-plugin-examples#400。如您所见,需要创建多个文件。这些文件将用于生成 OpenAPI 文档和验证接收到的查询是否有效(但这仍然是一个正在进行的功能,尚未可用)。
创建这些文件
query.go
:此文件定义查询的 Golang 类型。为了使自动迁移工作,重要的是您的查询扩展新的v0alpha1.CommonQueryProperties
。之后,只需定义您的查询自定义属性。query_test.go
:此测试文件既用于检查所有 JSON 文件是否与查询模型保持同步,也用于生成它们。第一次执行测试时,它会生成这些文件(因此请注意query.types.json
需要存在,即使它为空)。query.*.json
:自动生成的文件。这些模式可用于 OpenAPI 文档。
步骤 2:更改查询模型
一旦您的插件有了自己的模式,就可以开始引入模型更改了。由于同一主要版本(或同一 API 版本)内的查询需要兼容,请维护对旧数据格式的引用。此引用还有助于实现简单的迁移路径。
例如,假设您想要更改插件的查询格式,并且您使用的 Multiplier
属性正在更改为 Multiply
,如下所示
type DataQuery struct {
v0alpha1.CommonQueryProperties
- // Multiplier is the number to multiply the input by
+ // Multiply is the number to multiply the input by
+ Multiply int `json:"multiply,omitempty"`
+
+ // Deprecated: Use Multiply instead
Multiplier int `json:"multiplier,omitempty"`
}
在此示例中,您可以运行 query_test.go
中的测试来重新生成模式,这样您的新数据类型就可以使用了。
请注意,此时还没有破坏性更改,因为所有新参数(本例中为 Multiply
)都标记为可选。此外,其他业务逻辑都没有修改,因此一切都应该像以前一样工作,使用已弃用的属性。在下一步中,实际上会有一个破坏性更改。
步骤 3:使用新的查询格式
修改插件代码以使用新的数据模型,忽略现有仪表盘或查询继续使用旧模型的事实。请注意,您必须同时修改前端和后端代码。
首先,将 Multiplier
的使用替换为 Multiply
。
以下是一个后端示例
func (d *Datasource) query(ctx context.Context, pCtx backend.PluginContext, quer
return backend.DataResponse{}, fmt.Errorf("unmarshal: %w", err)
}
q := req.URL.Query()
- q.Add("multiplier", strconv.Itoa(input.Multiplier))
+ q.Add("multiplier", strconv.Itoa(input.Multiply))
req.URL.RawQuery = q.Encode()
}
httpResp, err := d.httpClient.Do(req)
以下是一个前端示例
export class QueryEditor extends PureComponent<Props> {
type="number"
id="multiplier"
name="multiplier"
- value={this.props.query.multiplier}
- onChange={(e) => this.props.onChange({ ...this.props.query, multiplier: e.currentTarget.valueAsNumber })}
+ value={this.props.query.multiply}
+ onChange={(e) => this.props.onChange({ ...this.props.query, multiply: e.currentTarget.valueAsNumber })}
/>
</HorizontalGroup>
);
此时,终于出现了破坏性更改。新查询将使用新格式并按预期工作,但旧查询不会,因为它们没有定义新属性。接下来我们将解决这个问题。
步骤 4:在后端添加迁移代码
在后端创建一个解析函数,该函数接收 QueryData
函数接收的通用 JSON blob,然后根据需要迁移格式。该函数应接收一个 backend.DataQuery
并返回您自己的 kinds.DataQuery
。
此函数应仅从原始 DataQuery
中解组 JSON,并将其解析为您自己的 DataQuery
,同时执行任何必要的迁移。在插件逻辑中使用此函数。通过此更改,在我们的示例中,无论查询使用的是旧模型 (`Multiplier`) 还是新模型 (`Multiply`),都能按预期工作。
示例
func (d *Datasource) query(ctx context.Context, pCtx backend.PluginContext, quer
return backend.DataResponse{}, fmt.Errorf("new request with context: %w", err)
}
if len(query.JSON) > 0 {
- input := &kinds.DataQuery{}
- err = json.Unmarshal(query.JSON, input)
+ input, err := convertQuery(query)
if err != nil {
- return backend.DataResponse{}, fmt.Errorf("unmarshal: %w", err)
+ return backend.DataResponse{}, err
}
q := req.URL.Query()
q.Add("multiplier", strconv.Itoa(input.Multiply))
...
+func convertQuery(orig backend.DataQuery) (*kinds.DataQuery, error) {
+ input := &kinds.DataQuery{}
+ err := json.Unmarshal(orig.JSON, input)
+ if err != nil {
+ return nil, fmt.Errorf("unmarshal: %w", err)
+ }
+ if input.Multiplier != 0 && input.Multiply == 0 {
+ input.Multiply = input.Multiplier
+ input.Multiplier = 0
+ }
+ return input, nil
+}
步骤 5:从前端使用迁移代码(使用实验性 API)
此功能取决于功能标志 grafanaAPIServerWithExperimentalAPIs。它还需要包 @grafana/runtime > 11.4(仍然是实验性功能)。如果您的插件实现此功能,请将其 grafanaDepencency 升级到 ">=11.4.0"。如果您的插件无法满足这些要求,请参阅使用旧 API 运行迁移。
您应该能够从前端和后端调用您的 convertQuery
函数,这样我们的 QueryEditor
组件就可以将查询转换为新格式。为了将此函数暴露给前端,后端需要实现 QueryConversionHandler
接口。这只是 convertQuery
函数的包装器,但适用于多个查询。请参阅此示例中如何实现此功能。
最后,调整前端,使 @grafana/runtime
知道是否应该运行迁移操作。分两步完成此操作
- 在插件的
DataSource
类中实现@grafana/runtime
暴露的MigrationHandler
。设置属性hasBackendMigration
(为true
)并实现函数shouldMigrate
。shouldMigrate
函数接收一个查询并验证它是否需要迁移(例如,通过检查最新属性或检查预期的插件版本,如果它是模型的一部分)。此验证避免了对后端进行不必要的查询。 - 将包装器
QueryEditorWithMigration
与您的QueryEditor
组件一起使用。此包装器将确保在渲染编辑器之前迁移查询。
就这样。一旦插件实现了这些步骤,现有和新查询将继续工作,而无需在多个地方重复迁移逻辑。
要查看步骤 2 到 5 的完整示例,请参阅此示例。
步骤 5(替代方案):使用旧 API 运行迁移
除了使用实验性 API 运行迁移之外,也可以使用旧 API 运行迁移。没有额外的要求。
请按照以下步骤操作
- 在后端,将
convertQuery
作为资源暴露,以便您可以使用诸如/migrate-query
之类的资源端点来检索它。 - 修复插件
QueryEditor
,因为旧查询试图渲染旧格式,而插件逻辑尚未为此做好准备。为此,请配置插件以使用刚刚定义的新迁移端点。
要查看步骤 2 到 5 的完整示例,请参阅此示例。
步骤 6(可选):添加 AdmissionHandler
此步骤是可选的。仅当您在 Grafana 中使用功能标志 grafanaAPIServerWithExperimentalAPIs 时才需要。
当 Grafana 运行时启用实验性 API 时,每个数据源将作为独立的 API 服务器运行。这意味着查询将被路由到诸如 https://<grafana-host>/apis/<datasource>.datasource.grafana.app/v0alpha1/namespaces/stack-1/connections/<uid>/query
的服务器。
在这种情况下,为了确保您的插件以预期的 API 版本(最初为 v0alpha1
)运行,请实现 AdmissionHandler
。此 AdmissionHandler
确保给定的数据源设置满足正在运行的 API 版本的预期,因此它们可以处理该 API 版本的查询。
当插件处于 v0*
阶段时,此步骤并非强制要求,但一旦插件达到 v1
,则成为强制要求。目前,它用于在保存数据源设置时验证数据源设置。
AdmissionHandler
方法应实现两个主要函数
ValidateAdmission
:用于检查给定实体(在此情况下是数据源设置)是否有效。MutateAdmission
:允许在存储实体之前对其进行修改。
在我们的示例中,这两个函数是可互换的,因为它们执行相同的代码(即,只进行验证,不进行任何修改)。
结论
添加查询迁移允许您的 Grafana 数据源插件在不破坏现有用户功能的情况下发展。通过在后端和前端都维护迁移处理程序,您可以确保兼容性并在每次更新时提供更流畅的用户体验。