为后端数据源插件添加查询迁移
随着插件的发展,插件定义的查询模型可能需要进行重大更改。原因包括插件依赖的第三方服务的重大更改、重构、新功能等。更改数据源查询模型时,插件应实现迁移逻辑,将现有查询从旧格式转换为最新格式。此迁移逻辑应包含在后端(对于非前端起源的查询,例如警报)和前端(以适应 QueryEditor
的新版本)。
为什么需要添加查询迁移处理器
为了确保兼容性和保持无缝性能,查询迁移处理器将旧查询转换为当前格式。这种方法允许您在不破坏现有查询或重复代码的情况下交付更新,当用户更新您的插件时,为用户提供平稳的过渡。
开始之前
根据您在指南中执行 以下步骤之一 的方法,您可能需要满足某些先决条件。这些先决条件是
- 必须将Grafana配置为以独立API服务器运行数据源,这种行为隐藏在功能标志grafanaAPIServerWithExperimentalAPIs之后。
- 插件必须在11.4或更高版本的Grafana上运行。
有关这些先决条件的更多信息,请参阅步骤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:使用新的查询格式
修改插件代码以使用新的数据模型,忽略现有仪表板或查询继续使用旧模型的事实。请注意,您必须修改前端和后端代码。
首先,用Multiply
替换对Multiplier
的使用。
下面是一个后端示例
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(仍然是实验性功能)。如果您的插件实现了此功能,则将其grafanaDependency
提升到“>=11.4.0”。如果您的插件无法满足这些要求,请参阅使用旧API运行迁移。
您应该能够从前端以及后台调用您的convertQuery
函数,因此我们的QueryEditor
组件应该能够将查询转换为新格式。为了将此函数暴露给前端,后端需要实现QueryConversionHandler
接口。这只是一个围绕convertQuery
函数的包装,但用于多个查询。有关如何在此示例中实现的信息。
最后,修改前端,以便@grafana/runtime
知道是否应该运行迁移操作。这需要两步完成
- 在插件的
DataSource
类中实现MigrationHandler
,该接口由@grafana/runtime
提供。将属性hasBackendMigration
(设置为true
)并实现shouldMigrate
函数。shouldMigrate
函数接收一个查询并验证它是否需要迁移(例如,通过检查最新的属性或检查预期的插件版本,如果它是模型的一部分)。这种验证避免了向后端发送不必要的查询。 - 使用包装器
QueryEditorWithMigration
与您的QueryEditor
组件一起使用。此包装器将确保在渲染编辑器之前迁移查询。
就是这样。一旦插件实现了这些步骤,现有和新查询将继续工作,无需在多个地方重复迁移逻辑。
要查看步骤2到5如何在完整示例中完成,请参阅此示例。
第5步(替代):使用旧API运行迁移
除了使用实验性API运行迁移外,还可以使用旧API运行迁移。没有其他要求。
按照以下步骤操作
- 在后端,将
convertQuery
公开为一个资源,以便您可以使用类似/migrate-query
的资源端点检索它。 - 修复插件
QueryEditor
,因为旧查询试图渲染旧格式,而插件逻辑没有为此做好准备。为此,配置插件使用刚刚定义的新迁移端点。
要查看步骤2到5如何在完整示例中完成,请参阅此示例。
第6步(可选):添加AdmissionHandler
此步骤是可选的。仅在您使用特征标志grafanaAPIServerWithExperimentalAPIs运行Grafana时才需要。
当使用实验性API运行Grafana时,每个数据源将作为一个独立的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 数据源插件在不破坏现有用户功能的情况下进行演变。通过在前后端都维护迁移处理器,您确保了兼容性并通过每次更新提供更流畅的用户体验。