mirror of https://github.com/grafana/grafana.git
Alerting: Keep the latest version of deleted rule in version table (#101481)
* add feature toggle alertRuleRestore * Update delete rule to require UserUID, remove all versions and create "delete" version that holds information about who and when deleted the rule
This commit is contained in:
parent
da2c382d80
commit
374380d1f6
|
|
@ -117,6 +117,7 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
|
|||
| `improvedExternalSessionHandling` | Enables improved support for OAuth external sessions. After enabling this feature, users might need to re-authenticate themselves. |
|
||||
| `elasticsearchCrossClusterSearch` | Enables cross cluster search in the Elasticsearch datasource |
|
||||
| `improvedExternalSessionHandlingSAML` | Enables improved support for SAML external sessions. Ensure the NameID format is correctly configured in Grafana for SAML Single Logout to function properly. |
|
||||
| `alertRuleRestore` | Enables the alert rule restore feature |
|
||||
|
||||
## Experimental feature toggles
|
||||
|
||||
|
|
|
|||
|
|
@ -255,4 +255,5 @@ export interface FeatureToggles {
|
|||
newShareReportDrawer?: boolean;
|
||||
rendererDisableAppPluginsPreload?: boolean;
|
||||
assetSriChecks?: boolean;
|
||||
alertRuleRestore?: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1782,6 +1782,13 @@ var (
|
|||
Owner: grafanaFrontendOpsWG,
|
||||
FrontendOnly: true,
|
||||
},
|
||||
{
|
||||
Name: "alertRuleRestore",
|
||||
Description: "Enables the alert rule restore feature",
|
||||
Stage: FeatureStagePublicPreview,
|
||||
Owner: grafanaAlertingSquad,
|
||||
Expression: "true", // enabled by default
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -236,3 +236,4 @@ alertingRuleVersionHistoryRestore,GA,@grafana/alerting-squad,false,false,true
|
|||
newShareReportDrawer,experimental,@grafana/sharing-squad,false,false,false
|
||||
rendererDisableAppPluginsPreload,experimental,@grafana/sharing-squad,false,false,true
|
||||
assetSriChecks,experimental,@grafana/frontend-ops,false,false,true
|
||||
alertRuleRestore,preview,@grafana/alerting-squad,false,false,false
|
||||
|
|
|
|||
|
|
|
@ -954,4 +954,8 @@ const (
|
|||
// FlagAssetSriChecks
|
||||
// Enables SRI checks for Grafana JavaScript assets
|
||||
FlagAssetSriChecks = "assetSriChecks"
|
||||
|
||||
// FlagAlertRuleRestore
|
||||
// Enables the alert rule restore feature
|
||||
FlagAlertRuleRestore = "alertRuleRestore"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -108,6 +108,22 @@
|
|||
"frontend": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "alertRuleRestore",
|
||||
"resourceVersion": "1741127758142",
|
||||
"creationTimestamp": "2025-03-04T22:29:36Z",
|
||||
"annotations": {
|
||||
"grafana.app/updatedTimestamp": "2025-03-04 22:35:58.1421143 +0000 UTC"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"description": "Enables the alert rule restore feature",
|
||||
"stage": "preview",
|
||||
"codeowner": "@grafana/alerting-squad",
|
||||
"expression": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "alertStateHistoryLokiOnly",
|
||||
|
|
|
|||
|
|
@ -1930,8 +1930,9 @@ func createTestEnv(t *testing.T, testConfig string) testEnvironment {
|
|||
Cfg: setting.UnifiedAlertingSettings{
|
||||
BaseInterval: time.Second * 10,
|
||||
},
|
||||
FolderService: folderService,
|
||||
Bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
|
||||
FolderService: folderService,
|
||||
Bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
|
||||
FeatureToggles: featuremgmt.WithFeatures(),
|
||||
}
|
||||
user := &user.SignedInUser{
|
||||
OrgID: 1,
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ func (srv RulerSrv) RouteDeleteAlertRules(c *contextmodel.ReqContext, namespaceU
|
|||
rulesToDelete = append(rulesToDelete, uid...)
|
||||
}
|
||||
if len(rulesToDelete) > 0 {
|
||||
err := srv.store.DeleteAlertRulesByUID(ctx, c.SignedInUser.GetOrgID(), rulesToDelete...)
|
||||
err := srv.store.DeleteAlertRulesByUID(ctx, c.SignedInUser.GetOrgID(), ngmodels.NewUserUID(c.SignedInUser), rulesToDelete...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -461,7 +461,7 @@ func (srv RulerSrv) updateAlertRulesInGroup(c *contextmodel.ReqContext, groupKey
|
|||
UIDs = append(UIDs, rule.UID)
|
||||
}
|
||||
|
||||
if err = srv.store.DeleteAlertRulesByUID(tranCtx, c.SignedInUser.GetOrgID(), UIDs...); err != nil {
|
||||
if err = srv.store.DeleteAlertRulesByUID(tranCtx, c.SignedInUser.GetOrgID(), ngmodels.NewUserUID(c.SignedInUser), UIDs...); err != nil {
|
||||
return fmt.Errorf("failed to delete rules: %w", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ func TestRouteDeleteAlertRules(t *testing.T) {
|
|||
deleteCommands := getRecordedCommand(ruleStore)
|
||||
require.Len(t, deleteCommands, 1)
|
||||
cmd := deleteCommands[0]
|
||||
actualUIDs := cmd.Params[1].([]string)
|
||||
actualUIDs := cmd.Params[2].([]string)
|
||||
require.Len(t, actualUIDs, len(expectedRules))
|
||||
for _, rule := range expectedRules {
|
||||
require.Containsf(t, actualUIDs, rule.UID, "Rule %s was expected to be deleted but it wasn't", rule.UID)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ type RuleStore interface {
|
|||
// and return the map of uuid to id.
|
||||
InsertAlertRules(ctx context.Context, user *ngmodels.UserUID, rules []ngmodels.AlertRule) ([]ngmodels.AlertRuleKeyWithId, error)
|
||||
UpdateAlertRules(ctx context.Context, user *ngmodels.UserUID, rules []ngmodels.UpdateRule) error
|
||||
DeleteAlertRulesByUID(ctx context.Context, orgID int64, ruleUID ...string) error
|
||||
DeleteAlertRulesByUID(ctx context.Context, orgID int64, user *ngmodels.UserUID, ruleUID ...string) error
|
||||
|
||||
// IncreaseVersionForAllRulesInNamespaces Increases version for all rules that have specified namespace uids
|
||||
IncreaseVersionForAllRulesInNamespaces(ctx context.Context, orgID int64, namespaceUIDs []string) ([]ngmodels.AlertRuleKeyWithVersion, error)
|
||||
|
|
|
|||
|
|
@ -556,7 +556,7 @@ func (service *AlertRuleService) persistDelta(ctx context.Context, user identity
|
|||
})
|
||||
}
|
||||
}
|
||||
if err := service.deleteRules(ctx, user.GetOrgID(), delta.Delete...); err != nil {
|
||||
if err := service.deleteRules(ctx, user, delta.Delete...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -749,7 +749,7 @@ func (service *AlertRuleService) DeleteAlertRule(ctx context.Context, user ident
|
|||
// This is different from deleting groups. We delete the rules directly rather than persisting a delta here to keep the semantics the same.
|
||||
// TODO: Either persist a delta here as a breaking change, or deprecate this endpoint in favor of the group endpoint.
|
||||
return service.xact.InTransaction(ctx, func(ctx context.Context) error {
|
||||
return service.deleteRules(ctx, user.GetOrgID(), rule)
|
||||
return service.deleteRules(ctx, user, rule)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -775,18 +775,18 @@ func (service *AlertRuleService) checkLimitsTransactionCtx(ctx context.Context,
|
|||
}
|
||||
|
||||
// deleteRules deletes a set of target rules and associated data, while checking for database consistency.
|
||||
func (service *AlertRuleService) deleteRules(ctx context.Context, orgID int64, targets ...*models.AlertRule) error {
|
||||
func (service *AlertRuleService) deleteRules(ctx context.Context, user identity.Requester, targets ...*models.AlertRule) error {
|
||||
uids := make([]string, 0, len(targets))
|
||||
for _, tgt := range targets {
|
||||
if tgt != nil {
|
||||
uids = append(uids, tgt.UID)
|
||||
}
|
||||
}
|
||||
if err := service.ruleStore.DeleteAlertRulesByUID(ctx, orgID, uids...); err != nil {
|
||||
if err := service.ruleStore.DeleteAlertRulesByUID(ctx, user.GetOrgID(), models.NewUserUID(user), uids...); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, uid := range uids {
|
||||
if err := service.provenanceStore.DeleteProvenance(ctx, &models.AlertRule{UID: uid}, orgID); err != nil {
|
||||
if err := service.provenanceStore.DeleteProvenance(ctx, &models.AlertRule{UID: uid}, user.GetOrgID()); err != nil {
|
||||
// We failed to clean up the record, but this doesn't break things. Log it and move on.
|
||||
service.log.Warn("Failed to delete provenance record for rule: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1726,7 +1726,7 @@ func TestDeleteRuleGroup(t *testing.T) {
|
|||
func TestDeleteRuleGroups(t *testing.T) {
|
||||
orgID1 := rand.Int63()
|
||||
orgID2 := rand.Int63()
|
||||
u := &user.SignedInUser{OrgID: orgID1}
|
||||
u := &user.SignedInUser{OrgID: orgID1, UserUID: "test-test"}
|
||||
|
||||
// Create groups across different orgs and namespaces
|
||||
groupKey1 := models.AlertRuleGroupKey{
|
||||
|
|
@ -1805,6 +1805,7 @@ func TestDeleteRuleGroups(t *testing.T) {
|
|||
// Verify only rules from group1 in org1 were deleted
|
||||
deletes := getDeletedRules(t, ruleStore)
|
||||
require.Len(t, deletes, 1)
|
||||
require.Equal(t, "test-test", deletes[0].userID)
|
||||
require.ElementsMatch(t, getUIDs(rules1), deletes[0].uids)
|
||||
})
|
||||
|
||||
|
|
@ -2045,8 +2046,9 @@ func getDeleteQueries(ruleStore *fakes.RuleStore) []fakes.GenericRecordedQuery {
|
|||
}
|
||||
|
||||
type deleteRuleOperation struct {
|
||||
orgID int64
|
||||
uids []string
|
||||
orgID int64
|
||||
userID string
|
||||
uids []string
|
||||
}
|
||||
|
||||
func getDeletedRules(t *testing.T, ruleStore *fakes.RuleStore) []deleteRuleOperation {
|
||||
|
|
@ -2058,12 +2060,20 @@ func getDeletedRules(t *testing.T, ruleStore *fakes.RuleStore) []deleteRuleOpera
|
|||
orgID, ok := q.Params[0].(int64)
|
||||
require.True(t, ok, "orgID parameter should be int64")
|
||||
|
||||
uids, ok := q.Params[1].([]string)
|
||||
uid := ""
|
||||
userUID, ok := q.Params[1].(*models.UserUID)
|
||||
require.True(t, ok, "parameter should be UserUID")
|
||||
if userUID != nil {
|
||||
uid = string(*userUID)
|
||||
}
|
||||
|
||||
uids, ok := q.Params[2].([]string)
|
||||
require.True(t, ok, "uids parameter should be []string")
|
||||
|
||||
operations = append(operations, deleteRuleOperation{
|
||||
orgID: orgID,
|
||||
uids: uids,
|
||||
orgID: orgID,
|
||||
userID: uid,
|
||||
uids: uids,
|
||||
})
|
||||
}
|
||||
return operations
|
||||
|
|
@ -2077,9 +2087,10 @@ func createAlertRuleService(t *testing.T, folderService folder.Service) AlertRul
|
|||
Cfg: setting.UnifiedAlertingSettings{
|
||||
BaseInterval: time.Second * 10,
|
||||
},
|
||||
Logger: log.NewNopLogger(),
|
||||
FolderService: folderService,
|
||||
Bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
|
||||
Logger: log.NewNopLogger(),
|
||||
FolderService: folderService,
|
||||
Bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
|
||||
FeatureToggles: featuremgmt.WithFeatures(),
|
||||
}
|
||||
// store := fakes.NewRuleStore(t)
|
||||
quotas := MockQuotaChecker{}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ type RuleStore interface {
|
|||
GetRuleGroupInterval(ctx context.Context, orgID int64, namespaceUID string, ruleGroup string) (int64, error)
|
||||
InsertAlertRules(ctx context.Context, user *models.UserUID, rule []models.AlertRule) ([]models.AlertRuleKeyWithId, error)
|
||||
UpdateAlertRules(ctx context.Context, user *models.UserUID, rule []models.UpdateRule) error
|
||||
DeleteAlertRulesByUID(ctx context.Context, orgID int64, ruleUID ...string) error
|
||||
DeleteAlertRulesByUID(ctx context.Context, orgID int64, user *models.UserUID, ruleUID ...string) error
|
||||
GetAlertRulesGroupByRuleUID(ctx context.Context, query *models.GetAlertRulesGroupByRuleUIDQuery) ([]*models.AlertRule, error)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,10 @@ var (
|
|||
)
|
||||
|
||||
// DeleteAlertRulesByUID is a handler for deleting an alert rule.
|
||||
func (st DBstore) DeleteAlertRulesByUID(ctx context.Context, orgID int64, ruleUID ...string) error {
|
||||
func (st DBstore) DeleteAlertRulesByUID(ctx context.Context, orgID int64, user *ngmodels.UserUID, ruleUID ...string) error {
|
||||
if len(ruleUID) == 0 {
|
||||
return nil
|
||||
}
|
||||
logger := st.Logger.New("org_id", orgID, "rule_uids", ruleUID)
|
||||
return st.SQLStore.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
|
||||
rows, err := sess.Table(alertRule{}).Where("org_id = ?", orgID).In("uid", ruleUID).Delete(alertRule{})
|
||||
|
|
@ -57,12 +60,6 @@ func (st DBstore) DeleteAlertRulesByUID(ctx context.Context, orgID int64, ruleUI
|
|||
})
|
||||
}
|
||||
|
||||
rows, err = sess.Table(alertRuleVersion{}).Where("rule_org_id = ?", orgID).In("rule_uid", ruleUID).Delete(alertRule{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Debug("Deleted alert rule versions", "count", rows)
|
||||
|
||||
rows, err = sess.Table("alert_instance").Where("rule_org_id = ?", orgID).In("rule_uid", ruleUID).Delete(alertRule{})
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -75,10 +72,78 @@ func (st DBstore) DeleteAlertRulesByUID(ctx context.Context, orgID int64, ruleUI
|
|||
}
|
||||
logger.Debug("Deleted alert rule state", "count", rows)
|
||||
|
||||
var versions []alertRuleVersion
|
||||
if st.FeatureToggles.IsEnabledGlobally(featuremgmt.FlagAlertRuleRestore) {
|
||||
versions, err = st.getLatestVersionOfRulesByUID(ctx, orgID, ruleUID)
|
||||
if err != nil {
|
||||
logger.Error("Failed to get latest version of deleted alert rules. The recovery will not be possible", "error", err)
|
||||
}
|
||||
for idx := range versions {
|
||||
version := &versions[idx]
|
||||
version.ID = 0
|
||||
version.RuleUID = ""
|
||||
version.Created = TimeNow()
|
||||
version.CreatedBy = nil
|
||||
if user != nil {
|
||||
version.CreatedBy = util.Pointer(string(*user))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rows, err = sess.Table(alertRuleVersion{}).Where("rule_org_id = ?", orgID).In("rule_uid", ruleUID).Delete(alertRule{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Debug("Deleted alert rule versions", "count", rows)
|
||||
|
||||
if len(versions) > 0 {
|
||||
_, err = sess.Insert(versions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to persist deleted rule for recovery: %w", err)
|
||||
}
|
||||
logger.Debug("Inserted alert rule versions for recovery", "count", len(versions))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (st DBstore) getLatestVersionOfRulesByUID(ctx context.Context, orgID int64, ruleUIDs []string) ([]alertRuleVersion, error) {
|
||||
var result []alertRuleVersion
|
||||
err := st.SQLStore.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
args, in := getINSubQueryArgs(ruleUIDs)
|
||||
// take only the latest versions of each rule by GUID
|
||||
rows, err := sess.SQL(fmt.Sprintf(`
|
||||
SELECT v1.* FROM alert_rule_version AS v1
|
||||
INNER JOIN (
|
||||
SELECT rule_guid, MAX(id) AS id
|
||||
FROM alert_rule_version
|
||||
WHERE rule_org_id = ?
|
||||
AND rule_uid IN (%s)
|
||||
GROUP BY rule_guid
|
||||
) AS v2 ON v1.rule_guid = v2.rule_guid AND v1.id = v2.id
|
||||
`, strings.Join(in, ",")), append([]any{orgID}, args...)...).Rows(new(alertRuleVersion))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result = make([]alertRuleVersion, 0, len(ruleUIDs))
|
||||
for rows.Next() {
|
||||
rule := new(alertRuleVersion)
|
||||
err = rows.Scan(rule)
|
||||
if err != nil {
|
||||
st.Logger.Error("Invalid rule version found in DB store, ignoring it", "func", "getLatestVersionOfRulesByUID", "error", err)
|
||||
continue
|
||||
}
|
||||
result = append(result, *rule)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// IncreaseVersionForAllRulesInNamespaces Increases version for all rules that have specified namespace. Returns all rules that belong to the namespaces
|
||||
func (st DBstore) IncreaseVersionForAllRulesInNamespaces(ctx context.Context, orgID int64, namespaceUIDs []string) ([]ngmodels.AlertRuleKeyWithVersion, error) {
|
||||
var keys []ngmodels.AlertRuleKeyWithVersion
|
||||
|
|
@ -820,7 +885,7 @@ func (st DBstore) DeleteInFolders(ctx context.Context, orgID int64, folderUIDs [
|
|||
}
|
||||
}
|
||||
|
||||
if err := st.DeleteAlertRulesByUID(ctx, orgID, uids...); err != nil {
|
||||
if err := st.DeleteAlertRulesByUID(ctx, orgID, ngmodels.NewUserUID(user), uids...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import (
|
|||
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/testutil"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
|
|
@ -685,7 +686,6 @@ func TestIntegration_DeleteInFolder(t *testing.T) {
|
|||
b := &fakeBus{}
|
||||
logger := log.New("test-dbstore")
|
||||
store := createTestStore(sqlStore, folderService, logger, cfg.UnifiedAlerting, b)
|
||||
|
||||
rule := createRule(t, store, nil)
|
||||
|
||||
t.Run("should not be able to delete folder without permissions to delete rules", func(t *testing.T) {
|
||||
|
|
@ -714,6 +714,9 @@ func TestIntegration_DeleteAlertRulesByUID(t *testing.T) {
|
|||
|
||||
sqlStore := db.InitTestDB(t)
|
||||
cfg := setting.NewCfg()
|
||||
cfg.UnifiedAlerting.BaseInterval = 1 * time.Second
|
||||
cfg.UnifiedAlerting.RuleVersionRecordLimit = -1
|
||||
|
||||
folderService := setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures())
|
||||
logger := log.New("test-dbstore")
|
||||
store := createTestStore(sqlStore, folderService, logger, cfg.UnifiedAlerting, &fakeBus{})
|
||||
|
|
@ -742,7 +745,7 @@ func TestIntegration_DeleteAlertRulesByUID(t *testing.T) {
|
|||
called = true
|
||||
return nil
|
||||
}
|
||||
err := store.DeleteAlertRulesByUID(context.Background(), rule.OrgID, rule.UID)
|
||||
err := store.DeleteAlertRulesByUID(context.Background(), rule.OrgID, &models.AlertingUserUID, rule.UID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, called)
|
||||
})
|
||||
|
|
@ -769,7 +772,7 @@ func TestIntegration_DeleteAlertRulesByUID(t *testing.T) {
|
|||
require.Len(t, savedInstances, 1)
|
||||
|
||||
// Delete the rule
|
||||
err = store.DeleteAlertRulesByUID(context.Background(), rule.OrgID, rule.UID)
|
||||
err = store.DeleteAlertRulesByUID(context.Background(), rule.OrgID, &models.AlertingUserUID, rule.UID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Now there should be no alert rule state
|
||||
|
|
@ -780,6 +783,71 @@ func TestIntegration_DeleteAlertRulesByUID(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.Empty(t, savedInstances)
|
||||
})
|
||||
|
||||
t.Run("should remove all version and insert one with empty rule_uid", func(t *testing.T) {
|
||||
orgID := int64(rand.Intn(1000))
|
||||
gen = gen.With(gen.WithOrgID(orgID))
|
||||
// Create a new store to pass the custom bus to check the signal
|
||||
b := &fakeBus{}
|
||||
logger := log.New("test-dbstore")
|
||||
|
||||
store := createTestStore(sqlStore, folderService, logger, cfg.UnifiedAlerting, b)
|
||||
store.FeatureToggles = featuremgmt.WithFeatures(featuremgmt.FlagAlertRuleRestore)
|
||||
|
||||
result, err := store.InsertAlertRules(context.Background(), &models.AlertingUserUID, gen.GenerateMany(3))
|
||||
uids := make([]string, 0, len(result))
|
||||
for _, rule := range result {
|
||||
uids = append(uids, rule.UID)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
rules, err := store.ListAlertRules(context.Background(), &models.ListAlertRulesQuery{OrgID: orgID, RuleUIDs: uids})
|
||||
require.NoError(t, err)
|
||||
|
||||
updates := make([]models.UpdateRule, 0, len(rules))
|
||||
for _, rule := range rules {
|
||||
rule2 := models.CopyRule(rule, gen.WithTitle(util.GenerateShortUID()))
|
||||
updates = append(updates, models.UpdateRule{
|
||||
Existing: rule,
|
||||
New: *rule2,
|
||||
})
|
||||
}
|
||||
err = store.UpdateAlertRules(context.Background(), &models.AlertingUserUID, updates)
|
||||
require.NoError(t, err)
|
||||
|
||||
versions, err := store.GetAlertRuleVersions(context.Background(), orgID, rules[0].GUID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, versions, 2)
|
||||
|
||||
err = store.DeleteAlertRulesByUID(context.Background(), orgID, util.Pointer(models.UserUID("test")), uids...)
|
||||
require.NoError(t, err)
|
||||
|
||||
guids := make([]string, 0, len(rules))
|
||||
for _, rule := range rules {
|
||||
guids = append(guids, rule.GUID)
|
||||
}
|
||||
|
||||
_ = sqlStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
var versions []alertRuleVersion
|
||||
err = sess.Table(alertRuleVersion{}).Where(`rule_uid = ''`).In("rule_guid", guids).Find(&versions)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, versions, len(rules)) // should be one version per GUID
|
||||
|
||||
for _, version := range versions {
|
||||
assert.Equal(t, "", version.RuleUID)
|
||||
assert.Equal(t, "test", *version.CreatedBy)
|
||||
// Remove the GUID from guids
|
||||
for i, guid := range guids {
|
||||
if guid == version.RuleGUID {
|
||||
guids = append(guids[:i], guids[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ensure that guids is empty
|
||||
assert.Empty(t, guids, "Some rules are left unrecoverable")
|
||||
return nil
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationInsertAlertRules(t *testing.T) {
|
||||
|
|
@ -1894,11 +1962,12 @@ func createTestStore(
|
|||
bus bus.Bus,
|
||||
) *DBstore {
|
||||
return &DBstore{
|
||||
SQLStore: sqlStore,
|
||||
FolderService: folderService,
|
||||
Logger: logger,
|
||||
Cfg: cfg,
|
||||
Bus: bus,
|
||||
SQLStore: sqlStore,
|
||||
FolderService: folderService,
|
||||
Logger: logger,
|
||||
Cfg: cfg,
|
||||
Bus: bus,
|
||||
FeatureToggles: featuremgmt.WithFeatures(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -102,10 +102,10 @@ func (f *RuleStore) GetRecordedCommands(predicate func(cmd any) (any, bool)) []a
|
|||
return result
|
||||
}
|
||||
|
||||
func (f *RuleStore) DeleteAlertRulesByUID(_ context.Context, orgID int64, UIDs ...string) error {
|
||||
func (f *RuleStore) DeleteAlertRulesByUID(_ context.Context, orgID int64, user *models.UserUID, UIDs ...string) error {
|
||||
f.RecordedOps = append(f.RecordedOps, GenericRecordedQuery{
|
||||
Name: "DeleteAlertRulesByUID",
|
||||
Params: []any{orgID, UIDs},
|
||||
Params: []any{orgID, user, UIDs},
|
||||
})
|
||||
|
||||
rules := f.Rules[orgID]
|
||||
|
|
|
|||
Loading…
Reference in New Issue