diff --git a/apps/alerting/rules/definitions/alerting-manifest.yaml b/apps/alerting/rules/definitions/alerting-manifest.yaml index f842608a627..2c7b9374a66 100644 --- a/apps/alerting/rules/definitions/alerting-manifest.yaml +++ b/apps/alerting/rules/definitions/alerting-manifest.yaml @@ -19,14 +19,24 @@ spec: additionalProperties: type: string type: object - data: + execErrState: + default: Error + enum: + - Error + - Ok + - Alerting + - KeepLast + type: string + expressions: additionalProperties: properties: datasourceUID: + description: The UID of the datasource to run this expression against. If omitted, the expression will be run against the `__expr__` datasource pattern: ^[a-zA-Z0-9_-]+$ type: string model: {} queryType: + description: The type of query if this is a query expression type: string relativeTimeRange: properties: @@ -41,21 +51,16 @@ spec: - to type: object source: + description: |- + Used to mark the expression to be used as the final source for the rule evaluation + Only one expression in a rule can be marked as the source + For AlertRules, this is the expression that will be evaluated against the alerting condition + For RecordingRules, this is the expression that will be recorded type: boolean required: - - queryType - - datasourceUID - model type: object type: object - execErrState: - default: Error - enum: - - Error - - Ok - - Alerting - - KeepLast - type: string for: allOf: - pattern: ^((([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?|0)$ @@ -151,10 +156,10 @@ spec: type: object required: - title - - data - trigger - noDataState - execErrState + - expressions type: object x-kubernetes-preserve-unknown-fields: true status: @@ -207,14 +212,16 @@ spec: schema: spec: properties: - data: + expressions: additionalProperties: properties: datasourceUID: + description: The UID of the datasource to run this expression against. If omitted, the expression will be run against the `__expr__` datasource pattern: ^[a-zA-Z0-9_-]+$ type: string model: {} queryType: + description: The type of query if this is a query expression type: string relativeTimeRange: properties: @@ -229,10 +236,13 @@ spec: - to type: object source: + description: |- + Used to mark the expression to be used as the final source for the rule evaluation + Only one expression in a rule can be marked as the source + For AlertRules, this is the expression that will be evaluated against the alerting condition + For RecordingRules, this is the expression that will be recorded type: boolean required: - - queryType - - datasourceUID - model type: object type: object @@ -263,9 +273,9 @@ spec: type: object required: - title - - data - trigger - metric + - expressions - targetDatasourceUID type: object x-kubernetes-preserve-unknown-fields: true diff --git a/apps/alerting/rules/definitions/alertrule.rules.alerting.grafana.app.yaml b/apps/alerting/rules/definitions/alertrule.rules.alerting.grafana.app.yaml index e7d9ab25aaa..e5fdfc57e5e 100644 --- a/apps/alerting/rules/definitions/alertrule.rules.alerting.grafana.app.yaml +++ b/apps/alerting/rules/definitions/alertrule.rules.alerting.grafana.app.yaml @@ -17,14 +17,24 @@ spec: additionalProperties: type: string type: object - data: + execErrState: + default: Error + enum: + - Error + - Ok + - Alerting + - KeepLast + type: string + expressions: additionalProperties: properties: datasourceUID: + description: The UID of the datasource to run this expression against. If omitted, the expression will be run against the `__expr__` datasource pattern: ^[a-zA-Z0-9_-]+$ type: string model: {} queryType: + description: The type of query if this is a query expression type: string relativeTimeRange: properties: @@ -39,21 +49,16 @@ spec: - to type: object source: + description: |- + Used to mark the expression to be used as the final source for the rule evaluation + Only one expression in a rule can be marked as the source + For AlertRules, this is the expression that will be evaluated against the alerting condition + For RecordingRules, this is the expression that will be recorded type: boolean required: - - queryType - - datasourceUID - model type: object type: object - execErrState: - default: Error - enum: - - Error - - Ok - - Alerting - - KeepLast - type: string for: allOf: - pattern: ^((([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?|0)$ @@ -149,10 +154,10 @@ spec: type: object required: - title - - data - trigger - noDataState - execErrState + - expressions type: object x-kubernetes-preserve-unknown-fields: true status: diff --git a/apps/alerting/rules/definitions/recordingrule.rules.alerting.grafana.app.yaml b/apps/alerting/rules/definitions/recordingrule.rules.alerting.grafana.app.yaml index b12bdf781f9..368bc5893c0 100644 --- a/apps/alerting/rules/definitions/recordingrule.rules.alerting.grafana.app.yaml +++ b/apps/alerting/rules/definitions/recordingrule.rules.alerting.grafana.app.yaml @@ -13,14 +13,16 @@ spec: properties: spec: properties: - data: + expressions: additionalProperties: properties: datasourceUID: + description: The UID of the datasource to run this expression against. If omitted, the expression will be run against the `__expr__` datasource pattern: ^[a-zA-Z0-9_-]+$ type: string model: {} queryType: + description: The type of query if this is a query expression type: string relativeTimeRange: properties: @@ -35,10 +37,13 @@ spec: - to type: object source: + description: |- + Used to mark the expression to be used as the final source for the rule evaluation + Only one expression in a rule can be marked as the source + For AlertRules, this is the expression that will be evaluated against the alerting condition + For RecordingRules, this is the expression that will be recorded type: boolean required: - - queryType - - datasourceUID - model type: object type: object @@ -69,9 +74,9 @@ spec: type: object required: - title - - data - trigger - metric + - expressions - targetDatasourceUID type: object x-kubernetes-preserve-unknown-fields: true diff --git a/apps/alerting/rules/kinds/v0alpha1/alertRule_spec.cue b/apps/alerting/rules/kinds/v0alpha1/alertRule_spec.cue index 0d074d28e6f..140005fe1d6 100644 --- a/apps/alerting/rules/kinds/v0alpha1/alertRule_spec.cue +++ b/apps/alerting/rules/kinds/v0alpha1/alertRule_spec.cue @@ -10,16 +10,16 @@ ExecErrState: *"Error" | "Ok" | "Alerting" | "KeepLast" // FIXME: the For and KeepFiringFor types should be using the AlertRulePromDuration type, but there seems to be an issue with the generator AlertRuleSpec: #RuleSpec & { - noDataState: NoDataState - execErrState: ExecErrState - "for"?: string & #PromDuration - keepFiringFor?: string & #PromDuration - missingSeriesEvalsToResolve?: int & >=0 - notificationSettings?: #NotificationSettings annotations?: { [string]: TemplateString } - panelRef?: #PanelRef + "for"?: string & #PromDuration + keepFiringFor?: string & #PromDuration + missingSeriesEvalsToResolve?: int & >=0 + noDataState: NoDataState + execErrState: ExecErrState + notificationSettings?: #NotificationSettings + panelRef?: #PanelRef } #PanelRef: { diff --git a/apps/alerting/rules/kinds/v0alpha1/rule_spec.cue b/apps/alerting/rules/kinds/v0alpha1/rule_spec.cue index 11c45d3b8fb..7b8cf55e734 100644 --- a/apps/alerting/rules/kinds/v0alpha1/rule_spec.cue +++ b/apps/alerting/rules/kinds/v0alpha1/rule_spec.cue @@ -12,12 +12,12 @@ TemplateString: string #RuleSpec: { title: string - data: #QueryMap paused?: bool trigger: #IntervalTrigger labels?: { [string]: TemplateString } + expressions: #ExpressionMap ... } @@ -34,15 +34,20 @@ TemplateString: string } // TODO: validate that only one can specify source=true -#QueryMap: { - [string]: #Query +#ExpressionMap: { + [string]: #Expression } // & struct.MinFields(1) This doesn't work in Cue ({ + interval: defaultPromDuration(), +}); + +export type PromDuration = string; + +export const defaultPromDuration = (): PromDuration => (""); + +export type TemplateString = string; + +export const defaultTemplateString = (): TemplateString => (""); + +// TODO(@moustafab): validate regex for time interval ref +export type TimeIntervalRef = string; + +export const defaultTimeIntervalRef = (): TimeIntervalRef => (""); + // TODO: validate that only one can specify source=true // & struct.MinFields(1) This doesn't work in Cue ; +export type ExpressionMap = Record; -export const defaultQueryMap = (): QueryMap => ({}); +export const defaultExpressionMap = (): ExpressionMap => ({}); -// TODO: come up with a better name for this. We have expression type things and data source queries -export interface Query { - // TODO: consider making this optional, with the nil value meaning "__expr__" (i.e. expression query) - queryType: string; +export interface Expression { + // The type of query if this is a query expression + queryType?: string; relativeTimeRange?: RelativeTimeRange; - datasourceUID: DatasourceUID; + // The UID of the datasource to run this expression against. If omitted, the expression will be run against the `__expr__` datasource + datasourceUID?: DatasourceUID; model: any; + // Used to mark the expression to be used as the final source for the rule evaluation + // Only one expression in a rule can be marked as the source + // For AlertRules, this is the expression that will be evaluated against the alerting condition + // For RecordingRules, this is the expression that will be recorded source?: boolean; } -export const defaultQuery = (): Query => ({ - queryType: "", - datasourceUID: defaultDatasourceUID(), +export const defaultExpression = (): Expression => ({ model: {}, }); @@ -40,37 +63,17 @@ export type DatasourceUID = string; export const defaultDatasourceUID = (): DatasourceUID => (""); -export interface IntervalTrigger { - interval: PromDuration; -} - -export const defaultIntervalTrigger = (): IntervalTrigger => ({ - interval: defaultPromDuration(), -}); - -export type PromDuration = string; - -export const defaultPromDuration = (): PromDuration => (""); - -// TODO(@moustafab): validate regex for time interval ref -export type TimeIntervalRef = string; - -export const defaultTimeIntervalRef = (): TimeIntervalRef => (""); - -export type TemplateString = string; - -export const defaultTemplateString = (): TemplateString => (""); - export interface Spec { title: string; - data: QueryMap; paused?: boolean; trigger: IntervalTrigger; - noDataState: string; - execErrState: string; + labels?: Record; + annotations?: Record; for?: string; keepFiringFor?: string; missingSeriesEvalsToResolve?: number; + noDataState: string; + execErrState: string; notificationSettings?: { receiver: string; groupBy?: string[]; @@ -80,8 +83,7 @@ export interface Spec { muteTimeIntervals?: TimeIntervalRef[]; activeTimeIntervals?: TimeIntervalRef[]; }; - annotations?: Record; - labels?: Record; + expressions: ExpressionMap; panelRef?: { dashboardUID: string; panelID: number; @@ -90,9 +92,9 @@ export interface Spec { export const defaultSpec = (): Spec => ({ title: "", - data: defaultQueryMap(), trigger: defaultIntervalTrigger(), noDataState: "NoData", execErrState: "Error", + expressions: defaultExpressionMap(), }); diff --git a/apps/alerting/rules/plugin/src/generated/recordingrule/v0alpha1/types.spec.gen.ts b/apps/alerting/rules/plugin/src/generated/recordingrule/v0alpha1/types.spec.gen.ts index f9944c8b4d7..fc8923debe6 100644 --- a/apps/alerting/rules/plugin/src/generated/recordingrule/v0alpha1/types.spec.gen.ts +++ b/apps/alerting/rules/plugin/src/generated/recordingrule/v0alpha1/types.spec.gen.ts @@ -1,24 +1,42 @@ // Code generated - EDITING IS FUTILE. DO NOT EDIT. +export interface IntervalTrigger { + interval: PromDuration; +} + +export const defaultIntervalTrigger = (): IntervalTrigger => ({ + interval: defaultPromDuration(), +}); + +export type PromDuration = string; + +export const defaultPromDuration = (): PromDuration => (""); + +export type TemplateString = string; + +export const defaultTemplateString = (): TemplateString => (""); + // TODO: validate that only one can specify source=true // & struct.MinFields(1) This doesn't work in Cue ; +export type ExpressionMap = Record; -export const defaultQueryMap = (): QueryMap => ({}); +export const defaultExpressionMap = (): ExpressionMap => ({}); -// TODO: come up with a better name for this. We have expression type things and data source queries -export interface Query { - // TODO: consider making this optional, with the nil value meaning "__expr__" (i.e. expression query) - queryType: string; +export interface Expression { + // The type of query if this is a query expression + queryType?: string; relativeTimeRange?: RelativeTimeRange; - datasourceUID: DatasourceUID; + // The UID of the datasource to run this expression against. If omitted, the expression will be run against the `__expr__` datasource + datasourceUID?: DatasourceUID; model: any; + // Used to mark the expression to be used as the final source for the rule evaluation + // Only one expression in a rule can be marked as the source + // For AlertRules, this is the expression that will be evaluated against the alerting condition + // For RecordingRules, this is the expression that will be recorded source?: boolean; } -export const defaultQuery = (): Query => ({ - queryType: "", - datasourceUID: defaultDatasourceUID(), +export const defaultExpression = (): Expression => ({ model: {}, }); @@ -40,37 +58,21 @@ export type DatasourceUID = string; export const defaultDatasourceUID = (): DatasourceUID => (""); -export interface IntervalTrigger { - interval: PromDuration; -} - -export const defaultIntervalTrigger = (): IntervalTrigger => ({ - interval: defaultPromDuration(), -}); - -export type PromDuration = string; - -export const defaultPromDuration = (): PromDuration => (""); - -export type TemplateString = string; - -export const defaultTemplateString = (): TemplateString => (""); - export interface Spec { title: string; - data: QueryMap; paused?: boolean; trigger: IntervalTrigger; - metric: string; labels?: Record; + metric: string; + expressions: ExpressionMap; targetDatasourceUID: string; } export const defaultSpec = (): Spec => ({ title: "", - data: defaultQueryMap(), trigger: defaultIntervalTrigger(), metric: "", + expressions: defaultExpressionMap(), targetDatasourceUID: "", }); diff --git a/pkg/registry/apps/alerting/rules/alertrule/compat.go b/pkg/registry/apps/alerting/rules/alertrule/compat.go index fbbb2be73d7..0ea9507c6e0 100644 --- a/pkg/registry/apps/alerting/rules/alertrule/compat.go +++ b/pkg/registry/apps/alerting/rules/alertrule/compat.go @@ -8,6 +8,7 @@ import ( "time" "github.com/grafana/grafana/pkg/apimachinery/utils" + "github.com/grafana/grafana/pkg/expr" "github.com/grafana/grafana/pkg/util" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -43,8 +44,8 @@ func convertToK8sResource( Labels: make(map[string]string), }, Spec: model.AlertRuleSpec{ - Title: rule.Title, - Data: make(map[string]model.AlertRuleQuery), + Title: rule.Title, + Expressions: make(model.AlertRuleExpressionMap), Trigger: model.AlertRuleIntervalTrigger{ Interval: model.AlertRulePromDuration(interval.String()), }, @@ -90,19 +91,7 @@ func convertToK8sResource( } for _, query := range rule.Data { - k8sQuery := model.AlertRuleQuery{ - QueryType: query.QueryType, - Model: query.Model, - DatasourceUID: model.AlertRuleDatasourceUID(query.DatasourceUID), - Source: util.Pointer(rule.Condition == query.RefID), - } - if time.Duration(query.RelativeTimeRange.From) > 0 || time.Duration(query.RelativeTimeRange.To) > 0 { - k8sQuery.RelativeTimeRange = &model.AlertRuleRelativeTimeRange{ - From: model.AlertRulePromDurationWMillis(query.RelativeTimeRange.From.String()), - To: model.AlertRulePromDurationWMillis(query.RelativeTimeRange.To.String()), - } - } - k8sRule.Spec.Data[query.RefID] = k8sQuery + k8sRule.Spec.Expressions[query.RefID] = convertToK8sExpression(query, rule) } for _, setting := range rule.NotificationSettings { @@ -158,6 +147,29 @@ func convertToK8sResource( return k8sRule, nil } +func convertToK8sExpression(query ngmodels.AlertQuery, rule *ngmodels.AlertRule) model.AlertRuleExpression { + expression := model.AlertRuleExpression{ + Model: query.Model, + } + if query.QueryType != "" { + expression.QueryType = util.Pointer(query.QueryType) + } + // DatasourceUID is optional and defaults to expr datasource + if !expr.IsDataSource(query.DatasourceUID) { + expression.DatasourceUID = util.Pointer(model.AlertRuleDatasourceUID(query.DatasourceUID)) + } + if time.Duration(query.RelativeTimeRange.From) > 0 || time.Duration(query.RelativeTimeRange.To) > 0 { + expression.RelativeTimeRange = &model.AlertRuleRelativeTimeRange{ + From: model.AlertRulePromDurationWMillis(query.RelativeTimeRange.From.String()), + To: model.AlertRulePromDurationWMillis(query.RelativeTimeRange.To.String()), + } + } + if rule.Condition == query.RefID { + expression.Source = util.Pointer(true) + } + return expression +} + func convertToK8sResources( orgID int64, rules []*ngmodels.AlertRule, @@ -201,7 +213,7 @@ func convertToBaseDomainModel(orgID int64, k8sRule *model.AlertRule) (*ngmodels. UID: k8sRule.Name, Title: k8sRule.Spec.Title, NamespaceUID: k8sRule.Namespace, - Data: make([]ngmodels.AlertQuery, 0, len(k8sRule.Spec.Data)), + Data: make([]ngmodels.AlertQuery, 0, len(k8sRule.Spec.Expressions)), IsPaused: k8sRule.Spec.Paused != nil && *k8sRule.Spec.Paused, Labels: make(map[string]string), Annotations: make(map[string]string), @@ -267,13 +279,13 @@ func convertToBaseDomainModel(orgID int64, k8sRule *model.AlertRule) (*ngmodels. } domainRule.IntervalSeconds = int64(time.Duration(interval).Seconds()) - for refID, query := range k8sRule.Spec.Data { - domainQuery, err := convertToDomainQuery(query, refID) + for refID, expression := range k8sRule.Spec.Expressions { + domainQuery, err := convertToDomainQuery(expression, refID) if err != nil { return nil, err } domainRule.Data = append(domainRule.Data, domainQuery) - if query.Source != nil && *query.Source { + if expression.Source != nil && *expression.Source { if domainRule.Condition != "" { return nil, fmt.Errorf("multiple queries marked as source: %s and %s", domainRule.Condition, refID) } @@ -339,23 +351,29 @@ func convertNotificationSettings(sourceSettings *model.AlertRuleV0alpha1SpecNoti return settings, nil } -func convertToDomainQuery(query model.AlertRuleQuery, refID string) (ngmodels.AlertQuery, error) { - modelJson, err := json.Marshal(query.Model) +func convertToDomainQuery(expression model.AlertRuleExpression, refID string) (ngmodels.AlertQuery, error) { + modelJson, err := json.Marshal(expression.Model) if err != nil { return ngmodels.AlertQuery{}, fmt.Errorf("failed to marshal model: %w", err) } domainQuery := ngmodels.AlertQuery{ - RefID: refID, - QueryType: query.QueryType, - DatasourceUID: string(query.DatasourceUID), - Model: modelJson, + RefID: refID, + Model: modelJson, } - if query.RelativeTimeRange != nil { - from, err := prom_model.ParseDuration(string(query.RelativeTimeRange.From)) + if expression.QueryType != nil { + domainQuery.QueryType = *expression.QueryType + } + if expression.DatasourceUID != nil { + domainQuery.DatasourceUID = string(*expression.DatasourceUID) + } else { + domainQuery.DatasourceUID = expr.DatasourceUID + } + if expression.RelativeTimeRange != nil { + from, err := prom_model.ParseDuration(string(expression.RelativeTimeRange.From)) if err != nil { return ngmodels.AlertQuery{}, fmt.Errorf("failed to parse duration: %w", err) } - to, err := prom_model.ParseDuration(string(query.RelativeTimeRange.To)) + to, err := prom_model.ParseDuration(string(expression.RelativeTimeRange.To)) if err != nil { return ngmodels.AlertQuery{}, fmt.Errorf("failed to parse duration: %w", err) } diff --git a/pkg/registry/apps/alerting/rules/recordingrule/compat.go b/pkg/registry/apps/alerting/rules/recordingrule/compat.go index f4479a41d48..e9ef0c4e295 100644 --- a/pkg/registry/apps/alerting/rules/recordingrule/compat.go +++ b/pkg/registry/apps/alerting/rules/recordingrule/compat.go @@ -9,6 +9,7 @@ import ( model "github.com/grafana/grafana/apps/alerting/rules/pkg/apis/alerting/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/utils" + "github.com/grafana/grafana/pkg/expr" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" @@ -42,8 +43,8 @@ func convertToK8sResource( Labels: make(map[string]string), }, Spec: model.RecordingRuleSpec{ - Title: rule.Title, - Data: make(map[string]model.RecordingRuleQuery), + Title: rule.Title, + Expressions: make(model.RecordingRuleExpressionMap), Trigger: model.RecordingRuleIntervalTrigger{ Interval: model.RecordingRulePromDuration(interval.String()), }, @@ -67,21 +68,7 @@ func convertToK8sResource( } for _, query := range rule.Data { - k8sQuery := model.RecordingRuleQuery{ - QueryType: query.QueryType, - Model: query.Model, - DatasourceUID: model.RecordingRuleDatasourceUID(query.DatasourceUID), - } - if time.Duration(query.RelativeTimeRange.From) > 0 || time.Duration(query.RelativeTimeRange.To) > 0 { - k8sQuery.RelativeTimeRange = &model.RecordingRuleRelativeTimeRange{ - From: model.RecordingRulePromDurationWMillis(query.RelativeTimeRange.From.String()), - To: model.RecordingRulePromDurationWMillis(query.RelativeTimeRange.To.String()), - } - } - if rule.Record != nil && rule.Record.From == query.RefID { - k8sQuery.Source = util.Pointer(true) - } - k8sRule.Spec.Data[query.RefID] = k8sQuery + k8sRule.Spec.Expressions[query.RefID] = convertToK8sExpression(query, rule) } meta, err := utils.MetaAccessor(k8sRule) @@ -108,6 +95,29 @@ func convertToK8sResource( return k8sRule, nil } +func convertToK8sExpression(query ngmodels.AlertQuery, rule *ngmodels.AlertRule) model.RecordingRuleExpression { + expression := model.RecordingRuleExpression{ + Model: query.Model, + } + if query.QueryType != "" { + expression.QueryType = util.Pointer(query.QueryType) + } + // DatasourceUID is optional and defaults to expr datasource + if !expr.IsDataSource(query.DatasourceUID) { + expression.DatasourceUID = util.Pointer(model.RecordingRuleDatasourceUID(query.DatasourceUID)) + } + if time.Duration(query.RelativeTimeRange.From) > 0 || time.Duration(query.RelativeTimeRange.To) > 0 { + expression.RelativeTimeRange = &model.RecordingRuleRelativeTimeRange{ + From: model.RecordingRulePromDurationWMillis(query.RelativeTimeRange.From.String()), + To: model.RecordingRulePromDurationWMillis(query.RelativeTimeRange.To.String()), + } + } + if rule.Record != nil && rule.Record.From == query.RefID { + expression.Source = util.Pointer(true) + } + return expression +} + func convertToK8sResources( orgID int64, rules []*ngmodels.AlertRule, @@ -150,7 +160,7 @@ func convertToBaseDomainModel(orgID int64, k8sRule *model.RecordingRule) (*ngmod OrgID: orgID, UID: k8sRule.Name, Title: k8sRule.Spec.Title, - Data: make([]ngmodels.AlertQuery, 0, len(k8sRule.Spec.Data)), + Data: make([]ngmodels.AlertQuery, 0, len(k8sRule.Spec.Expressions)), IsPaused: k8sRule.Spec.Paused != nil && *k8sRule.Spec.Paused, Labels: make(map[string]string), @@ -187,35 +197,13 @@ func convertToBaseDomainModel(orgID int64, k8sRule *model.RecordingRule) (*ngmod for k, v := range k8sRule.Spec.Labels { domainRule.Labels[k] = string(v) } - for refID, query := range k8sRule.Spec.Data { - modelJson, err := json.Marshal(query.Model) + for refID, expression := range k8sRule.Spec.Expressions { + domainQuery, err := convertToDomainQuery(expression, refID) if err != nil { - return nil, fmt.Errorf("failed to marshal model: %w", err) + return nil, err } - domainQuery := ngmodels.AlertQuery{ - RefID: refID, - QueryType: query.QueryType, - DatasourceUID: string(query.DatasourceUID), - Model: modelJson, - } - if query.RelativeTimeRange != nil { - from, err := prom_model.ParseDuration(string(query.RelativeTimeRange.From)) - if err != nil { - return nil, fmt.Errorf("failed to parse duration: %w", err) - } - to, err := prom_model.ParseDuration(string(query.RelativeTimeRange.To)) - if err != nil { - return nil, fmt.Errorf("failed to parse duration: %w", err) - } - domainQuery.RelativeTimeRange = ngmodels.RelativeTimeRange{ - From: ngmodels.Duration(from), - To: ngmodels.Duration(to), - } - } - domainRule.Data = append(domainRule.Data, domainQuery) - - if query.Source != nil && *query.Source { + if expression.Source != nil && *expression.Source { if domainRule.Record.From != "" { return nil, fmt.Errorf("multiple queries marked as source: %s and %s", domainRule.Record.From, refID) } @@ -227,3 +215,37 @@ func convertToBaseDomainModel(orgID int64, k8sRule *model.RecordingRule) (*ngmod } return domainRule, nil } + +func convertToDomainQuery(expression model.RecordingRuleExpression, refID string) (ngmodels.AlertQuery, error) { + modelJson, err := json.Marshal(expression.Model) + if err != nil { + return ngmodels.AlertQuery{}, fmt.Errorf("failed to marshal model: %w", err) + } + domainQuery := ngmodels.AlertQuery{ + RefID: refID, + Model: modelJson, + } + if expression.QueryType != nil { + domainQuery.QueryType = *expression.QueryType + } + if expression.DatasourceUID != nil { + domainQuery.DatasourceUID = string(*expression.DatasourceUID) + } else { + domainQuery.DatasourceUID = expr.DatasourceUID + } + if expression.RelativeTimeRange != nil { + from, err := prom_model.ParseDuration(string(expression.RelativeTimeRange.From)) + if err != nil { + return ngmodels.AlertQuery{}, fmt.Errorf("failed to parse duration: %w", err) + } + to, err := prom_model.ParseDuration(string(expression.RelativeTimeRange.To)) + if err != nil { + return ngmodels.AlertQuery{}, fmt.Errorf("failed to parse duration: %w", err) + } + domainQuery.RelativeTimeRange = ngmodels.RelativeTimeRange{ + From: ngmodels.Duration(from), + To: ngmodels.Duration(to), + } + } + return domainQuery, nil +} diff --git a/pkg/tests/apis/alerting/rules/alertrule/alertrule_test.go b/pkg/tests/apis/alerting/rules/alertrule/alertrule_test.go index 9daedc259ae..5d4a8b67497 100644 --- a/pkg/tests/apis/alerting/rules/alertrule/alertrule_test.go +++ b/pkg/tests/apis/alerting/rules/alertrule/alertrule_test.go @@ -54,10 +54,10 @@ func TestIntegrationResourceIdentifier(t *testing.T) { }, Spec: v0alpha1.AlertRuleSpec{ Title: rule.Title, - Data: map[string]v0alpha1.AlertRuleQuery{ + Expressions: v0alpha1.AlertRuleExpressionMap{ "A": { - QueryType: "query", - DatasourceUID: v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID), + QueryType: util.Pointer("query"), + DatasourceUID: util.Pointer(v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.AlertRuleRelativeTimeRange{ @@ -154,10 +154,10 @@ func TestIntegrationAccessControl(t *testing.T) { }, Spec: v0alpha1.AlertRuleSpec{ Title: rule.Title, - Data: map[string]v0alpha1.AlertRuleQuery{ + Expressions: v0alpha1.AlertRuleExpressionMap{ "A": { - QueryType: "query", - DatasourceUID: v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID), + QueryType: util.Pointer(rule.Data[0].QueryType), + DatasourceUID: util.Pointer(v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.AlertRuleRelativeTimeRange{ @@ -241,10 +241,10 @@ func TestIntegrationCRUD(t *testing.T) { }, Spec: v0alpha1.AlertRuleSpec{ Title: rule.Title, - Data: map[string]v0alpha1.AlertRuleQuery{ + Expressions: v0alpha1.AlertRuleExpressionMap{ "A": { - QueryType: "query", - DatasourceUID: v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID), + QueryType: util.Pointer(rule.Data[0].QueryType), + DatasourceUID: util.Pointer(v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.AlertRuleRelativeTimeRange{ @@ -295,10 +295,10 @@ func TestIntegrationCRUD(t *testing.T) { }, Spec: v0alpha1.AlertRuleSpec{ Title: rule.Title, - Data: map[string]v0alpha1.AlertRuleQuery{ + Expressions: v0alpha1.AlertRuleExpressionMap{ "A": { - QueryType: "query", - DatasourceUID: v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID), + QueryType: util.Pointer(rule.Data[0].QueryType), + DatasourceUID: util.Pointer(v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.AlertRuleRelativeTimeRange{ @@ -328,8 +328,8 @@ func TestIntegrationCRUD(t *testing.T) { }, }, Spec: v0alpha1.AlertRuleSpec{ - Title: "invalid-rule", - Data: map[string]v0alpha1.AlertRuleQuery{}, // Empty data should fail + Title: "invalid-rule", + Expressions: v0alpha1.AlertRuleExpressionMap{}, // Empty data should fail Trigger: v0alpha1.AlertRuleIntervalTrigger{ Interval: "30", }, @@ -356,10 +356,10 @@ func TestIntegrationCRUD(t *testing.T) { }, Spec: v0alpha1.AlertRuleSpec{ Title: rule.Title, - Data: map[string]v0alpha1.AlertRuleQuery{ + Expressions: v0alpha1.AlertRuleExpressionMap{ "A": { - QueryType: "query", - DatasourceUID: v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID), + QueryType: util.Pointer(rule.Data[0].QueryType), + DatasourceUID: util.Pointer(v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.AlertRuleRelativeTimeRange{ @@ -408,10 +408,10 @@ func TestIntegrationCRUD(t *testing.T) { }, Spec: v0alpha1.AlertRuleSpec{ Title: rule.Title, - Data: map[string]v0alpha1.AlertRuleQuery{ + Expressions: v0alpha1.AlertRuleExpressionMap{ "A": { - QueryType: "query", - DatasourceUID: v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID), + QueryType: util.Pointer(rule.Data[0].QueryType), + DatasourceUID: util.Pointer(v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, RelativeTimeRange: &v0alpha1.AlertRuleRelativeTimeRange{ From: v0alpha1.AlertRulePromDurationWMillis("5m"), @@ -445,10 +445,10 @@ func TestIntegrationCRUD(t *testing.T) { }, Spec: v0alpha1.AlertRuleSpec{ Title: rule.Title, - Data: map[string]v0alpha1.AlertRuleQuery{ + Expressions: v0alpha1.AlertRuleExpressionMap{ "A": { - QueryType: "query", - DatasourceUID: v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID), + QueryType: util.Pointer(rule.Data[0].QueryType), + DatasourceUID: util.Pointer(v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.AlertRuleRelativeTimeRange{ @@ -501,10 +501,10 @@ func TestIntegrationPatch(t *testing.T) { }, Spec: v0alpha1.AlertRuleSpec{ Title: rule.Title, - Data: map[string]v0alpha1.AlertRuleQuery{ + Expressions: v0alpha1.AlertRuleExpressionMap{ "A": { - QueryType: "query", - DatasourceUID: v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID), + QueryType: util.Pointer(rule.Data[0].QueryType), + DatasourceUID: util.Pointer(v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.AlertRuleRelativeTimeRange{ diff --git a/pkg/tests/apis/alerting/rules/compat/alertrule_test.go b/pkg/tests/apis/alerting/rules/compat/alertrule_test.go index d5339bdf7c7..4eb2cc39b52 100644 --- a/pkg/tests/apis/alerting/rules/compat/alertrule_test.go +++ b/pkg/tests/apis/alerting/rules/compat/alertrule_test.go @@ -57,10 +57,10 @@ func TestIntegrationAlertRuleCompatCreateViaK8s(t *testing.T) { }, Spec: v0alpha1.AlertRuleSpec{ Title: rule.Title, - Data: map[string]v0alpha1.AlertRuleQuery{ + Expressions: v0alpha1.AlertRuleExpressionMap{ "A": { - QueryType: "query", - DatasourceUID: v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID), + QueryType: util.Pointer(rule.Data[0].QueryType), + DatasourceUID: util.Pointer(v0alpha1.AlertRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.AlertRuleRelativeTimeRange{ @@ -91,9 +91,9 @@ func TestIntegrationAlertRuleCompatCreateViaK8s(t *testing.T) { err := json.Unmarshal(retrievedRule.Data[0].Model, &model) require.NoError(t, err) require.NotNil(t, model) - expectedModel, ok := created.Spec.Data["A"].Model.(map[string]interface{}) + expectedModel, ok := created.Spec.Expressions["A"].Model.(map[string]interface{}) if !ok { - t.Fatalf("Expected model to be a map[string]interface{}, got %T", created.Spec.Data["A"].Model) + t.Fatalf("Expected model to be a map[string]interface{}, got %T", created.Spec.Expressions["A"].Model) } for k, v := range expectedModel { require.EqualValues(t, v, model[k], "Model field %s should match", k) @@ -230,13 +230,13 @@ func TestIntegrationAlertRuleCompatCreateViaProvisioning(t *testing.T) { require.NoError(t, err) require.NotNil(t, retrievedRule) require.Equal(t, r.Title, retrievedRule.Spec.Title) - require.NotNil(t, retrievedRule.Spec.Data[r.Data[0].RefID].Source) - require.True(t, *retrievedRule.Spec.Data[r.Data[0].RefID].Source) + require.NotNil(t, retrievedRule.Spec.Expressions[r.Data[0].RefID].Source) + require.True(t, *retrievedRule.Spec.Expressions[r.Data[0].RefID].Source) require.Equal(t, r.FolderUID, retrievedRule.Annotations["grafana.app/folder"]) require.Equal(t, created.Title, retrievedRule.Labels[v0alpha1.GroupLabelKey]) require.Equal(t, fmt.Sprintf("%d", i), retrievedRule.Labels[v0alpha1.GroupIndexLabelKey]) require.Equal(t, ngmodels.ProvenanceAPI, ngmodels.Provenance(retrievedRule.GetProvenanceStatus())) - require.EqualValues(t, r.Data[0].DatasourceUID, retrievedRule.Spec.Data["A"].DatasourceUID) + require.EqualValues(t, r.Data[0].DatasourceUID, *retrievedRule.Spec.Expressions["A"].DatasourceUID) expectedDuration, err := prom_model.ParseDuration(fmt.Sprintf("%ds", created.Interval)) require.NoError(t, err) require.Equal(t, expectedDuration.String(), string(retrievedRule.Spec.Trigger.Interval)) @@ -244,9 +244,9 @@ func TestIntegrationAlertRuleCompatCreateViaProvisioning(t *testing.T) { err = json.Unmarshal(r.Data[0].Model, &expectedModel) require.NoError(t, err) require.NotNil(t, expectedModel) - retrievedModel, ok := retrievedRule.Spec.Data["A"].Model.(map[string]interface{}) + retrievedModel, ok := retrievedRule.Spec.Expressions["A"].Model.(map[string]interface{}) if !ok { - t.Fatalf("Expected model to be a map[string]interface{}, got %T", retrievedRule.Spec.Data["A"].Model) + t.Fatalf("Expected model to be a map[string]interface{}, got %T", retrievedRule.Spec.Expressions["A"].Model) } for k, v := range expectedModel { require.EqualValues(t, v, retrievedModel[k], "Model field %s should match", k) @@ -372,13 +372,13 @@ func TestIntegrationAlertRuleCompatCreateViaProvisioningChangeGroupInK8s(t *test require.NoError(t, err) require.NotNil(t, retrievedRule) require.Equal(t, r.Title, retrievedRule.Spec.Title) - require.NotNil(t, retrievedRule.Spec.Data[r.Data[0].RefID].Source) - require.True(t, *retrievedRule.Spec.Data[r.Data[0].RefID].Source) + require.NotNil(t, retrievedRule.Spec.Expressions[r.Data[0].RefID].Source) + require.True(t, *retrievedRule.Spec.Expressions[r.Data[0].RefID].Source) require.Equal(t, r.FolderUID, retrievedRule.Annotations["grafana.app/folder"]) require.Equal(t, created.Title, retrievedRule.Labels[v0alpha1.GroupLabelKey]) require.Equal(t, fmt.Sprintf("%d", i), retrievedRule.Labels[v0alpha1.GroupIndexLabelKey]) require.Equal(t, ngmodels.ProvenanceAPI, ngmodels.Provenance(retrievedRule.GetProvenanceStatus())) - require.EqualValues(t, r.Data[0].DatasourceUID, retrievedRule.Spec.Data["X"].DatasourceUID) + require.EqualValues(t, r.Data[0].DatasourceUID, *retrievedRule.Spec.Expressions["X"].DatasourceUID) expectedDuration, err := prom_model.ParseDuration(fmt.Sprintf("%ds", created.Interval)) require.NoError(t, err) require.Equal(t, expectedDuration.String(), string(retrievedRule.Spec.Trigger.Interval)) @@ -386,9 +386,9 @@ func TestIntegrationAlertRuleCompatCreateViaProvisioningChangeGroupInK8s(t *test err = json.Unmarshal(r.Data[0].Model, &expectedModel) require.NoError(t, err) require.NotNil(t, expectedModel) - retrievedModel, ok := retrievedRule.Spec.Data["X"].Model.(map[string]interface{}) + retrievedModel, ok := retrievedRule.Spec.Expressions["X"].Model.(map[string]interface{}) if !ok { - t.Fatalf("Expected model to be a map[string]interface{}, got %T", retrievedRule.Spec.Data["X"].Model) + t.Fatalf("Expected model to be a map[string]interface{}, got %T", retrievedRule.Spec.Expressions["X"].Model) } for k, v := range expectedModel { require.EqualValues(t, v, retrievedModel[k], "Model field %s should match", k) diff --git a/pkg/tests/apis/alerting/rules/compat/recordingrule_test.go b/pkg/tests/apis/alerting/rules/compat/recordingrule_test.go index a254901c72f..25f4dbf243b 100644 --- a/pkg/tests/apis/alerting/rules/compat/recordingrule_test.go +++ b/pkg/tests/apis/alerting/rules/compat/recordingrule_test.go @@ -64,10 +64,10 @@ func TestIntegrationRecordingRuleCompatCreateViaK8s(t *testing.T) { Spec: v0alpha1.RecordingRuleSpec{ Title: rule.Title, Metric: rule.Record.Metric, - Data: map[string]v0alpha1.RecordingRuleQuery{ + Expressions: v0alpha1.RecordingRuleExpressionMap{ "A": { - QueryType: "query", - DatasourceUID: v0alpha1.RecordingRuleDatasourceUID(rule.Data[0].DatasourceUID), + QueryType: util.Pointer(rule.Data[0].QueryType), + DatasourceUID: util.Pointer(v0alpha1.RecordingRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.RecordingRuleRelativeTimeRange{ @@ -98,9 +98,9 @@ func TestIntegrationRecordingRuleCompatCreateViaK8s(t *testing.T) { err := json.Unmarshal(retrievedRule.Data[0].Model, &model) require.NoError(t, err) require.NotNil(t, model) - expectedModel, ok := created.Spec.Data["A"].Model.(map[string]interface{}) + expectedModel, ok := created.Spec.Expressions["A"].Model.(map[string]interface{}) if !ok { - t.Fatalf("Expected model to be a map[string]interface{}, got %T", created.Spec.Data["A"].Model) + t.Fatalf("Expected model to be a map[string]interface{}, got %T", created.Spec.Expressions["A"].Model) } for k, v := range expectedModel { require.EqualValues(t, v, model[k], "Model field %s should match", k) @@ -247,7 +247,7 @@ func TestIntegrationRecordingRuleCompatCreateViaProvisioning(t *testing.T) { require.Equal(t, created.Title, retrievedRule.Labels[v0alpha1.GroupLabelKey]) require.Equal(t, fmt.Sprintf("%d", i), retrievedRule.Labels[v0alpha1.GroupIndexLabelKey]) require.Equal(t, ngmodels.ProvenanceAPI, ngmodels.Provenance(retrievedRule.GetProvenanceStatus())) - require.EqualValues(t, r.Data[0].DatasourceUID, retrievedRule.Spec.Data["A"].DatasourceUID) + require.EqualValues(t, r.Data[0].DatasourceUID, *retrievedRule.Spec.Expressions["A"].DatasourceUID) expectedDuration, err := prom_model.ParseDuration(fmt.Sprintf("%ds", created.Interval)) require.NoError(t, err) require.Equal(t, expectedDuration.String(), string(retrievedRule.Spec.Trigger.Interval)) @@ -255,9 +255,9 @@ func TestIntegrationRecordingRuleCompatCreateViaProvisioning(t *testing.T) { err = json.Unmarshal(r.Data[0].Model, &expectedModel) require.NoError(t, err) require.NotNil(t, expectedModel) - retrievedModel, ok := retrievedRule.Spec.Data["A"].Model.(map[string]interface{}) + retrievedModel, ok := retrievedRule.Spec.Expressions["A"].Model.(map[string]interface{}) if !ok { - t.Fatalf("Expected model to be a map[string]interface{}, got %T", retrievedRule.Spec.Data["A"].Model) + t.Fatalf("Expected model to be a map[string]interface{}, got %T", retrievedRule.Spec.Expressions["A"].Model) } for k, v := range expectedModel { require.EqualValues(t, v, retrievedModel[k], "Model field %s should match", k) @@ -391,7 +391,7 @@ func TestIntegrationRecordingRuleCompatCreateViaProvisioningChangeGroupInK8s(t * require.Equal(t, created.Title, retrievedRule.Labels[v0alpha1.GroupLabelKey]) require.Equal(t, fmt.Sprintf("%d", i), retrievedRule.Labels[v0alpha1.GroupIndexLabelKey]) require.Equal(t, ngmodels.ProvenanceAPI, ngmodels.Provenance(retrievedRule.GetProvenanceStatus())) - require.EqualValues(t, r.Data[0].DatasourceUID, retrievedRule.Spec.Data["X"].DatasourceUID) + require.EqualValues(t, r.Data[0].DatasourceUID, *retrievedRule.Spec.Expressions["X"].DatasourceUID) expectedDuration, err := prom_model.ParseDuration(fmt.Sprintf("%ds", created.Interval)) require.NoError(t, err) require.Equal(t, expectedDuration.String(), string(retrievedRule.Spec.Trigger.Interval)) @@ -399,9 +399,9 @@ func TestIntegrationRecordingRuleCompatCreateViaProvisioningChangeGroupInK8s(t * err = json.Unmarshal(r.Data[0].Model, &expectedModel) require.NoError(t, err) require.NotNil(t, expectedModel) - retrievedModel, ok := retrievedRule.Spec.Data["X"].Model.(map[string]interface{}) + retrievedModel, ok := retrievedRule.Spec.Expressions["X"].Model.(map[string]interface{}) if !ok { - t.Fatalf("Expected model to be a map[string]interface{}, got %T", retrievedRule.Spec.Data["X"].Model) + t.Fatalf("Expected model to be a map[string]interface{}, got %T", retrievedRule.Spec.Expressions["X"].Model) } for k, v := range expectedModel { require.EqualValues(t, v, retrievedModel[k], "Model field %s should match", k) diff --git a/pkg/tests/apis/alerting/rules/recordingrule/recordingrule_test.go b/pkg/tests/apis/alerting/rules/recordingrule/recordingrule_test.go index df706099124..1f024e9fb7f 100644 --- a/pkg/tests/apis/alerting/rules/recordingrule/recordingrule_test.go +++ b/pkg/tests/apis/alerting/rules/recordingrule/recordingrule_test.go @@ -55,10 +55,10 @@ func TestIntegrationResourceIdentifier(t *testing.T) { Spec: v0alpha1.RecordingRuleSpec{ Title: rule.Title, Metric: rule.Record.Metric, - Data: map[string]v0alpha1.RecordingRuleQuery{ + Expressions: v0alpha1.RecordingRuleExpressionMap{ "A": { - QueryType: "query", - DatasourceUID: v0alpha1.RecordingRuleDatasourceUID(rule.Data[0].DatasourceUID), + QueryType: util.Pointer(rule.Data[0].QueryType), + DatasourceUID: util.Pointer(v0alpha1.RecordingRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.RecordingRuleRelativeTimeRange{ @@ -155,10 +155,10 @@ func TestIntegrationAccessControl(t *testing.T) { Spec: v0alpha1.RecordingRuleSpec{ Title: rule.Title, Metric: rule.Record.Metric, - Data: map[string]v0alpha1.RecordingRuleQuery{ + Expressions: v0alpha1.RecordingRuleExpressionMap{ "A": { - QueryType: "query", - DatasourceUID: v0alpha1.RecordingRuleDatasourceUID(rule.Data[0].DatasourceUID), + QueryType: util.Pointer(rule.Data[0].QueryType), + DatasourceUID: util.Pointer(v0alpha1.RecordingRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.RecordingRuleRelativeTimeRange{ @@ -242,10 +242,10 @@ func TestIntegrationCRUD(t *testing.T) { Spec: v0alpha1.RecordingRuleSpec{ Title: rule.Title, Metric: rule.Record.Metric, - Data: map[string]v0alpha1.RecordingRuleQuery{ + Expressions: v0alpha1.RecordingRuleExpressionMap{ "A": { - QueryType: "query", - DatasourceUID: v0alpha1.RecordingRuleDatasourceUID(rule.Data[0].DatasourceUID), + QueryType: util.Pointer(rule.Data[0].QueryType), + DatasourceUID: util.Pointer(v0alpha1.RecordingRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.RecordingRuleRelativeTimeRange{ @@ -294,10 +294,10 @@ func TestIntegrationCRUD(t *testing.T) { Spec: v0alpha1.RecordingRuleSpec{ Title: rule.Title, Metric: rule.Record.Metric, - Data: map[string]v0alpha1.RecordingRuleQuery{ + Expressions: v0alpha1.RecordingRuleExpressionMap{ "A": { - QueryType: "query", - DatasourceUID: v0alpha1.RecordingRuleDatasourceUID(rule.Data[0].DatasourceUID), + QueryType: util.Pointer(rule.Data[0].QueryType), + DatasourceUID: util.Pointer(v0alpha1.RecordingRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.RecordingRuleRelativeTimeRange{ @@ -325,8 +325,8 @@ func TestIntegrationCRUD(t *testing.T) { }, }, Spec: v0alpha1.RecordingRuleSpec{ - Title: "invalid-recording-rule", - Data: map[string]v0alpha1.RecordingRuleQuery{}, // Empty data should fail + Title: "invalid-recording-rule", + Expressions: v0alpha1.RecordingRuleExpressionMap{}, // Empty data should fail Trigger: v0alpha1.RecordingRuleIntervalTrigger{ Interval: "30s", }, @@ -352,10 +352,10 @@ func TestIntegrationCRUD(t *testing.T) { Spec: v0alpha1.RecordingRuleSpec{ Title: rule.Title, Metric: rule.Record.Metric, - Data: map[string]v0alpha1.RecordingRuleQuery{ + Expressions: v0alpha1.RecordingRuleExpressionMap{ "A": { - QueryType: "query", - DatasourceUID: v0alpha1.RecordingRuleDatasourceUID(rule.Data[0].DatasourceUID), + QueryType: util.Pointer(rule.Data[0].QueryType), + DatasourceUID: util.Pointer(v0alpha1.RecordingRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.RecordingRuleRelativeTimeRange{ @@ -404,10 +404,10 @@ func TestIntegrationCRUD(t *testing.T) { Spec: v0alpha1.RecordingRuleSpec{ Title: rule.Title, Metric: rule.Record.Metric, - Data: map[string]v0alpha1.RecordingRuleQuery{ + Expressions: v0alpha1.RecordingRuleExpressionMap{ "A": { - QueryType: "query", - DatasourceUID: v0alpha1.RecordingRuleDatasourceUID(rule.Data[0].DatasourceUID), + QueryType: util.Pointer(rule.Data[0].QueryType), + DatasourceUID: util.Pointer(v0alpha1.RecordingRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, RelativeTimeRange: &v0alpha1.RecordingRuleRelativeTimeRange{ From: v0alpha1.RecordingRulePromDurationWMillis("5m"), @@ -440,10 +440,10 @@ func TestIntegrationCRUD(t *testing.T) { Spec: v0alpha1.RecordingRuleSpec{ Title: rule.Title, Metric: rule.Record.Metric, - Data: map[string]v0alpha1.RecordingRuleQuery{ + Expressions: v0alpha1.RecordingRuleExpressionMap{ "A": { - QueryType: "query", - DatasourceUID: v0alpha1.RecordingRuleDatasourceUID(rule.Data[0].DatasourceUID), + QueryType: util.Pointer(rule.Data[0].QueryType), + DatasourceUID: util.Pointer(v0alpha1.RecordingRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.RecordingRuleRelativeTimeRange{ @@ -496,10 +496,10 @@ func TestIntegrationPatch(t *testing.T) { Spec: v0alpha1.RecordingRuleSpec{ Title: rule.Title, Metric: rule.Record.Metric, - Data: map[string]v0alpha1.RecordingRuleQuery{ + Expressions: v0alpha1.RecordingRuleExpressionMap{ "A": { - QueryType: "query", - DatasourceUID: v0alpha1.RecordingRuleDatasourceUID(rule.Data[0].DatasourceUID), + QueryType: util.Pointer(rule.Data[0].QueryType), + DatasourceUID: util.Pointer(v0alpha1.RecordingRuleDatasourceUID(rule.Data[0].DatasourceUID)), Model: rule.Data[0].Model, Source: util.Pointer(true), RelativeTimeRange: &v0alpha1.RecordingRuleRelativeTimeRange{ diff --git a/pkg/tests/apis/openapi_snapshots/rules.alerting.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/rules.alerting.grafana.app-v0alpha1.json index d26d36d1225..afd80701b8c 100644 --- a/pkg/tests/apis/openapi_snapshots/rules.alerting.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/rules.alerting.grafana.app-v0alpha1.json @@ -2369,10 +2369,10 @@ "type": "object", "required": [ "title", - "data", "trigger", "noDataState", - "execErrState" + "execErrState", + "expressions" ], "properties": { "annotations": { @@ -2381,22 +2381,32 @@ "type": "string" } }, - "data": { + "execErrState": { + "type": "string", + "default": "Error", + "enum": [ + "Error", + "Ok", + "Alerting", + "KeepLast" + ] + }, + "expressions": { "type": "object", "additionalProperties": { "type": "object", "required": [ - "queryType", - "datasourceUID", "model" ], "properties": { "datasourceUID": { + "description": "The UID of the datasource to run this expression against. If omitted, the expression will be run against the `__expr__` datasource", "type": "string", "pattern": "^[a-zA-Z0-9_-]+$" }, "model": {}, "queryType": { + "description": "The type of query if this is a query expression", "type": "string" }, "relativeTimeRange": { @@ -2417,21 +2427,12 @@ } }, "source": { + "description": "Used to mark the expression to be used as the final source for the rule evaluation\nOnly one expression in a rule can be marked as the source\nFor AlertRules, this is the expression that will be evaluated against the alerting condition\nFor RecordingRules, this is the expression that will be recorded", "type": "boolean" } } } }, - "execErrState": { - "type": "string", - "default": "Error", - "enum": [ - "Error", - "Ok", - "Alerting", - "KeepLast" - ] - }, "for": { "type": "string", "allOf": [ @@ -2730,28 +2731,28 @@ "type": "object", "required": [ "title", - "data", "trigger", "metric", + "expressions", "targetDatasourceUID" ], "properties": { - "data": { + "expressions": { "type": "object", "additionalProperties": { "type": "object", "required": [ - "queryType", - "datasourceUID", "model" ], "properties": { "datasourceUID": { + "description": "The UID of the datasource to run this expression against. If omitted, the expression will be run against the `__expr__` datasource", "type": "string", "pattern": "^[a-zA-Z0-9_-]+$" }, "model": {}, "queryType": { + "description": "The type of query if this is a query expression", "type": "string" }, "relativeTimeRange": { @@ -2772,6 +2773,7 @@ } }, "source": { + "description": "Used to mark the expression to be used as the final source for the rule evaluation\nOnly one expression in a rule can be marked as the source\nFor AlertRules, this is the expression that will be evaluated against the alerting condition\nFor RecordingRules, this is the expression that will be recorded", "type": "boolean" } } diff --git a/public/app/api/clients/rules/v0alpha1/endpoints.gen.ts b/public/app/api/clients/rules/v0alpha1/endpoints.gen.ts index 01247d97dd1..604940505db 100644 --- a/public/app/api/clients/rules/v0alpha1/endpoints.gen.ts +++ b/public/app/api/clients/rules/v0alpha1/endpoints.gen.ts @@ -846,19 +846,25 @@ export type AlertRuleSpec = { annotations?: { [key: string]: string; }; - data: { + execErrState: 'Error' | 'Ok' | 'Alerting' | 'KeepLast'; + expressions: { [key: string]: { - datasourceUID: string; + /** The UID of the datasource to run this expression against. If omitted, the expression will be run against the `__expr__` datasource */ + datasourceUID?: string; model: any; - queryType: string; + /** The type of query if this is a query expression */ + queryType?: string; relativeTimeRange?: { from: string; to: string; }; + /** Used to mark the expression to be used as the final source for the rule evaluation + Only one expression in a rule can be marked as the source + For AlertRules, this is the expression that will be evaluated against the alerting condition + For RecordingRules, this is the expression that will be recorded */ source?: boolean; }; }; - execErrState: 'Error' | 'Ok' | 'Alerting' | 'KeepLast'; for?: any & any; keepFiringFor?: any & any; labels?: { @@ -982,15 +988,21 @@ export type Status = { }; export type Patch = object; export type RecordingRuleSpec = { - data: { + expressions: { [key: string]: { - datasourceUID: string; + /** The UID of the datasource to run this expression against. If omitted, the expression will be run against the `__expr__` datasource */ + datasourceUID?: string; model: any; - queryType: string; + /** The type of query if this is a query expression */ + queryType?: string; relativeTimeRange?: { from: string; to: string; }; + /** Used to mark the expression to be used as the final source for the rule evaluation + Only one expression in a rule can be marked as the source + For AlertRules, this is the expression that will be evaluated against the alerting condition + For RecordingRules, this is the expression that will be recorded */ source?: boolean; }; };