package store import ( "encoding/json" "fmt" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/services/ngalert/models" ) // We only care about the data source UIDs. type compactQuery struct { DatasourceUID string `json:"datasourceUid"` } // AlertRuleConvertOptions controls which fields to parse during conversion from alertRule to models.AlertRule. // By default all fields are included. Set Exclude* to true to skip parsing expensive fields. type AlertRuleConvertOptions struct { ExcludeAlertQueries bool // Only parse datasource UIDs from queries ExcludeNotificationSettings bool ExcludeMetadata bool } func alertRuleToModelsAlertRule(ar alertRule, l log.Logger) (models.AlertRule, error) { return convertAlertRuleToModel(ar, l, AlertRuleConvertOptions{}) } // convertAlertRuleToModel creates a models.AlertRule from an alertRule. // opts.Exclude* fields control which expensive fields to skip parsing, reducing JSON serializations. func convertAlertRuleToModel(ar alertRule, l log.Logger, opts AlertRuleConvertOptions) (models.AlertRule, error) { var data []models.AlertQuery if opts.ExcludeAlertQueries { var cqs []compactQuery if err := json.Unmarshal([]byte(ar.Data), &cqs); err != nil { return models.AlertRule{}, fmt.Errorf("failed to parse data: %w", err) } for _, cq := range cqs { data = append(data, models.AlertQuery{DatasourceUID: cq.DatasourceUID}) } } else { if err := json.Unmarshal([]byte(ar.Data), &data); err != nil { return models.AlertRule{}, fmt.Errorf("failed to parse data: %w", err) } } result := models.AlertRule{ ID: ar.ID, OrgID: ar.OrgID, GUID: ar.GUID, Title: ar.Title, Condition: ar.Condition, Data: data, Updated: ar.Updated, IntervalSeconds: ar.IntervalSeconds, Version: ar.Version, UID: ar.UID, NamespaceUID: ar.NamespaceUID, DashboardUID: ar.DashboardUID, PanelID: ar.PanelID, RuleGroupIndex: ar.RuleGroupIndex, For: ar.For, KeepFiringFor: ar.KeepFiringFor, IsPaused: ar.IsPaused, MissingSeriesEvalsToResolve: ar.MissingSeriesEvalsToResolve, } if ar.RuleGroup == "" { noGroupRuleGroup, err := models.NewNoGroupRuleGroup(ar.UID) if err != nil { return models.AlertRule{}, fmt.Errorf("failed to create no group rule group: %w", err) } result.RuleGroup = noGroupRuleGroup.String() } else { result.RuleGroup = ar.RuleGroup } if ar.UpdatedBy != nil { result.UpdatedBy = util.Pointer(models.UserUID(*ar.UpdatedBy)) } var err error if ar.NoDataState != "" { result.NoDataState, err = models.NoDataStateFromString(ar.NoDataState) if err != nil { l.Warn("Unknown NoDataState value, defaulting to NoData", append(result.GetKey().LogContext(), "original", ar.NoDataState)...) result.NoDataState = models.NoData } } if ar.ExecErrState != "" { result.ExecErrState, err = models.ErrStateFromString(ar.ExecErrState) if err != nil { l.Warn("Unknown ExecErrState value, defaulting to Error", append(result.GetKey().LogContext(), "original", ar.ExecErrState)...) result.ExecErrState = models.ErrorErrState } } if ar.Record != "" { var record models.Record err = json.Unmarshal([]byte(ar.Record), &record) if err != nil { return models.AlertRule{}, fmt.Errorf("failed to parse record: %w", err) } result.Record = &record } if ar.Labels != "" { err = json.Unmarshal([]byte(ar.Labels), &result.Labels) if err != nil { return models.AlertRule{}, fmt.Errorf("failed to parse labels: %w", err) } } if ar.Annotations != "" { err = json.Unmarshal([]byte(ar.Annotations), &result.Annotations) if err != nil { return models.AlertRule{}, fmt.Errorf("failed to parse annotations: %w", err) } } if !opts.ExcludeNotificationSettings && ar.NotificationSettings != "" { ns, err := parseNotificationSettings(ar.NotificationSettings) if err != nil { return models.AlertRule{}, fmt.Errorf("failed to parse notification settings: %w", err) } result.NotificationSettings = ns } if !opts.ExcludeMetadata && ar.Metadata != "" { err = json.Unmarshal([]byte(ar.Metadata), &result.Metadata) if err != nil { return models.AlertRule{}, fmt.Errorf("failed to metadata: %w", err) } } return result, nil } func parseNotificationSettings(s string) ([]models.NotificationSettings, error) { var result []models.NotificationSettings if err := json.Unmarshal([]byte(s), &result); err != nil { return nil, err } return result, nil } func alertRuleFromModelsAlertRule(ar models.AlertRule) (alertRule, error) { result := alertRule{ ID: ar.ID, GUID: ar.GUID, OrgID: ar.OrgID, Title: ar.Title, Condition: ar.Condition, Updated: ar.Updated, IntervalSeconds: ar.IntervalSeconds, Version: ar.Version, UID: ar.UID, NamespaceUID: ar.NamespaceUID, DashboardUID: ar.DashboardUID, PanelID: ar.PanelID, RuleGroupIndex: ar.RuleGroupIndex, NoDataState: ar.NoDataState.String(), ExecErrState: ar.ExecErrState.String(), For: ar.For, KeepFiringFor: ar.KeepFiringFor, IsPaused: ar.IsPaused, MissingSeriesEvalsToResolve: ar.MissingSeriesEvalsToResolve, } if models.IsNoGroupRuleGroup(ar.RuleGroup) { result.RuleGroup = "" } else { result.RuleGroup = ar.RuleGroup } if ar.UpdatedBy != nil { result.UpdatedBy = util.Pointer(string(*ar.UpdatedBy)) } // Serialize complex types to JSON strings data, err := json.Marshal(ar.Data) if err != nil { return alertRule{}, fmt.Errorf("failed to marshal data: %w", err) } result.Data = string(data) if ar.Record != nil { recordData, err := json.Marshal(ar.Record) if err != nil { return alertRule{}, fmt.Errorf("failed to marshal record: %w", err) } result.Record = string(recordData) } if len(ar.Annotations) > 0 { annotationsData, err := json.Marshal(ar.Annotations) if err != nil { return alertRule{}, fmt.Errorf("failed to marshal annotations: %w", err) } result.Annotations = string(annotationsData) } if len(ar.Labels) > 0 { labelsData, err := json.Marshal(ar.Labels) if err != nil { return alertRule{}, fmt.Errorf("failed to marshal labels: %w", err) } result.Labels = string(labelsData) } if len(ar.NotificationSettings) > 0 { notificationSettingsData, err := json.Marshal(ar.NotificationSettings) if err != nil { return alertRule{}, fmt.Errorf("failed to marshal notification settings: %w", err) } result.NotificationSettings = string(notificationSettingsData) } metadata, err := json.Marshal(ar.Metadata) if err != nil { return alertRule{}, fmt.Errorf("failed to metadata: %w", err) } result.Metadata = string(metadata) return result, nil } func alertRuleToAlertRuleVersion(rule alertRule) alertRuleVersion { return alertRuleVersion{ RuleOrgID: rule.OrgID, RuleGUID: rule.GUID, RuleUID: rule.UID, RuleNamespaceUID: rule.NamespaceUID, RuleGroup: rule.RuleGroup, RuleGroupIndex: rule.RuleGroupIndex, ParentVersion: 0, RestoredFrom: 0, Version: rule.Version, Created: rule.Updated, // assuming the Updated time as the creation time CreatedBy: rule.UpdatedBy, Message: "", // Message is set by caller when creating versions Title: rule.Title, Condition: rule.Condition, Data: rule.Data, IntervalSeconds: rule.IntervalSeconds, Record: rule.Record, NoDataState: rule.NoDataState, ExecErrState: rule.ExecErrState, For: rule.For, KeepFiringFor: rule.KeepFiringFor, Annotations: rule.Annotations, Labels: rule.Labels, IsPaused: rule.IsPaused, NotificationSettings: rule.NotificationSettings, Metadata: rule.Metadata, MissingSeriesEvalsToResolve: rule.MissingSeriesEvalsToResolve, } } func alertRuleVersionToAlertRule(version alertRuleVersion) alertRule { return alertRule{ ID: version.ID, GUID: version.RuleGUID, OrgID: version.RuleOrgID, Title: version.Title, Condition: version.Condition, Data: version.Data, Updated: version.Created, UpdatedBy: version.CreatedBy, IntervalSeconds: version.IntervalSeconds, Version: version.Version, UID: version.RuleUID, NamespaceUID: version.RuleNamespaceUID, // Versions do not store Dashboard\Panel as separate column. // However, these fields are part of annotations and information in these fields is redundant DashboardUID: nil, PanelID: nil, RuleGroup: version.RuleGroup, RuleGroupIndex: version.RuleGroupIndex, Record: version.Record, NoDataState: version.NoDataState, ExecErrState: version.ExecErrState, For: version.For, KeepFiringFor: version.KeepFiringFor, Annotations: version.Annotations, Labels: version.Labels, IsPaused: version.IsPaused, NotificationSettings: version.NotificationSettings, Metadata: version.Metadata, MissingSeriesEvalsToResolve: version.MissingSeriesEvalsToResolve, } } func alertRuleVersionToModelsAlertRuleVersion(version alertRuleVersion, l log.Logger) (models.AlertRuleVersion, error) { result, err := alertRuleToModelsAlertRule(alertRuleVersionToAlertRule(version), l) if err != nil { return models.AlertRuleVersion{}, err } return models.AlertRuleVersion{ AlertRule: result, Message: version.Message, }, nil }