Alerting: Rule version history API (#99041)
* implement store method to read rule versions * implement request handler * declare a new endpoint * fix fake to return correct response * add tests * add integration tests * rename history to versions * apply diff from swagger CI step Signed-off-by: Yuri Tseretyan <yuriy.tseretyan@grafana.com> --------- Signed-off-by: Yuri Tseretyan <yuriy.tseretyan@grafana.com>
This commit is contained in:
@@ -241,6 +241,18 @@ func TestIntegrationAlertRulePermissions(t *testing.T) {
|
||||
require.Len(t, export.Groups, 1)
|
||||
require.Equal(t, expected, export.Groups[0])
|
||||
})
|
||||
|
||||
t.Run("Get versions of any rule", func(t *testing.T) {
|
||||
for _, groups := range allRules { // random rule from each folder
|
||||
group := groups[rand.Intn(len(groups))]
|
||||
rule := group.Rules[rand.Intn(len(group.Rules))]
|
||||
versions, status, raw := apiClient.GetRuleVersionsWithStatus(t, rule.GrafanaManagedAlert.UID)
|
||||
if assert.Equalf(t, http.StatusOK, status, "Expected status 200, got %d: %s", status, raw) {
|
||||
assert.NotEmpty(t, versions)
|
||||
assert.Equal(t, rule, versions[0]) // the first version in the collection should always be the current
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when permissions for folder2 removed", func(t *testing.T) {
|
||||
@@ -310,6 +322,12 @@ func TestIntegrationAlertRulePermissions(t *testing.T) {
|
||||
require.Equal(t, http.StatusForbidden, status)
|
||||
})
|
||||
|
||||
t.Run("Versions of rule", func(t *testing.T) {
|
||||
uid := allRules["folder2"][0].Rules[0].GrafanaManagedAlert.UID
|
||||
_, status, raw := apiClient.GetRuleVersionsWithStatus(t, uid)
|
||||
require.Equalf(t, http.StatusForbidden, status, "Expected status 403, got %d: %s", status, raw)
|
||||
})
|
||||
|
||||
t.Run("when all permissions are revoked", func(t *testing.T) {
|
||||
removeFolderPermission(t, permissionsStore, 1, userID, org.RoleEditor, "folder1")
|
||||
apiClient.ReloadCachedPermissions(t)
|
||||
@@ -4315,6 +4333,95 @@ func TestIntegrationRuleUpdateAllDatabases(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationRuleVersions(t *testing.T) {
|
||||
testinfra.SQLiteIntegrationTest(t)
|
||||
|
||||
// Setup Grafana and its Database
|
||||
dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
EnableQuota: true,
|
||||
DisableAnonymous: true,
|
||||
AppModeProduction: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p)
|
||||
|
||||
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
||||
DefaultOrgRole: string(org.RoleEditor),
|
||||
Password: "password",
|
||||
Login: "grafana",
|
||||
})
|
||||
|
||||
apiClient := newAlertingApiClient(grafanaListedAddr, "grafana", "password")
|
||||
|
||||
// Create the namespace we'll save our alerts to.
|
||||
apiClient.CreateFolder(t, "folder1", "folder1")
|
||||
|
||||
postGroupRaw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1-post.json"))
|
||||
require.NoError(t, err)
|
||||
var group1 apimodels.PostableRuleGroupConfig
|
||||
require.NoError(t, json.Unmarshal(postGroupRaw, &group1))
|
||||
|
||||
// Create rule under folder1
|
||||
response := apiClient.PostRulesGroup(t, "folder1", &group1)
|
||||
|
||||
require.NotEmptyf(t, response.Created, "Expected created to be set")
|
||||
uid := response.Created[0]
|
||||
|
||||
ruleV1 := apiClient.GetRuleByUID(t, uid)
|
||||
|
||||
t.Run("should return 1 version right after creation", func(t *testing.T) {
|
||||
versions, status, raw := apiClient.GetRuleVersionsWithStatus(t, uid)
|
||||
require.Equalf(t, http.StatusOK, status, "Expected status 200, got %d: %s", status, raw)
|
||||
require.Lenf(t, versions, 1, "Expected 1 version, got %d", len(versions))
|
||||
assert.Equal(t, ruleV1, versions[0])
|
||||
})
|
||||
|
||||
group1Gettable := apiClient.GetRulesGroup(t, "folder1", group1.Name)
|
||||
group1 = convertGettableRuleGroupToPostable(group1Gettable.GettableRuleGroupConfig)
|
||||
group1.Rules[0].Annotations[util.GenerateShortUID()] = util.GenerateShortUID()
|
||||
|
||||
_ = apiClient.PostRulesGroup(t, "folder1", &group1)
|
||||
|
||||
ruleV2 := apiClient.GetRuleByUID(t, uid)
|
||||
|
||||
t.Run("should return previous versions after update", func(t *testing.T) {
|
||||
versions, status, raw := apiClient.GetRuleVersionsWithStatus(t, uid)
|
||||
require.Equalf(t, http.StatusOK, status, "Expected status 200, got %d: %s", status, raw)
|
||||
require.Lenf(t, versions, 2, "Expected 2 versions, got %d", len(versions))
|
||||
|
||||
pathsToIgnore := []string{
|
||||
"GrafanaManagedAlert.ID", // In versions ID has different value
|
||||
}
|
||||
// compare expected and actual and ignore the dynamic fields
|
||||
diff := cmp.Diff(apimodels.GettableRuleVersions{ruleV2, ruleV1}, versions, cmp.FilterPath(func(path cmp.Path) bool {
|
||||
for _, s := range pathsToIgnore {
|
||||
if strings.Contains(path.String(), s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, cmp.Ignore()))
|
||||
assert.Empty(t, diff)
|
||||
})
|
||||
|
||||
_ = apiClient.PostRulesGroup(t, "folder1", &group1) // Noop update
|
||||
|
||||
t.Run("should not add new version if rule was not changed", func(t *testing.T) {
|
||||
versions, status, raw := apiClient.GetRuleVersionsWithStatus(t, uid)
|
||||
require.Equalf(t, http.StatusOK, status, "Expected status 200, got %d: %s", status, raw)
|
||||
require.Lenf(t, versions, 2, "Expected 2 versions, got %d", len(versions))
|
||||
})
|
||||
|
||||
apiClient.DeleteRulesGroup(t, "folder1", group1.Name)
|
||||
|
||||
t.Run("should NotFound after rule was deleted", func(t *testing.T) {
|
||||
_, status, raw := apiClient.GetRuleVersionsWithStatus(t, uid)
|
||||
require.Equalf(t, http.StatusNotFound, status, "Expected status 404, got %d: %s", status, raw)
|
||||
})
|
||||
}
|
||||
|
||||
func newTestingRuleConfig(t *testing.T) apimodels.PostableRuleGroupConfig {
|
||||
interval, err := model.ParseDuration("1m")
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -475,6 +475,13 @@ func (a apiClient) PostRulesGroupWithStatus(t *testing.T, folder string, group *
|
||||
return m, resp.StatusCode, string(b)
|
||||
}
|
||||
|
||||
func (a apiClient) PostRulesGroup(t *testing.T, folder string, group *apimodels.PostableRuleGroupConfig) apimodels.UpdateRuleGroupResponse {
|
||||
t.Helper()
|
||||
m, status, raw := a.PostRulesGroupWithStatus(t, folder, group)
|
||||
requireStatusCode(t, http.StatusAccepted, status, raw)
|
||||
return m
|
||||
}
|
||||
|
||||
func (a apiClient) PostRulesExportWithStatus(t *testing.T, folder string, group *apimodels.PostableRuleGroupConfig, params *apimodels.ExportQueryParams) (int, string) {
|
||||
t.Helper()
|
||||
buf := bytes.Buffer{}
|
||||
@@ -1048,6 +1055,22 @@ func (a apiClient) GetActiveAlertsWithStatus(t *testing.T) (apimodels.AlertGroup
|
||||
return sendRequest[apimodels.AlertGroups](t, req, http.StatusOK)
|
||||
}
|
||||
|
||||
func (a apiClient) GetRuleVersionsWithStatus(t *testing.T, ruleUID string) (apimodels.GettableRuleVersions, int, string) {
|
||||
t.Helper()
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/ruler/grafana/api/v1/rule/%s/versions", a.url, ruleUID), nil)
|
||||
require.NoError(t, err)
|
||||
return sendRequest[apimodels.GettableRuleVersions](t, req, http.StatusOK)
|
||||
}
|
||||
|
||||
func (a apiClient) GetRuleByUID(t *testing.T, ruleUID string) apimodels.GettableExtendedRuleNode {
|
||||
t.Helper()
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/ruler/grafana/api/v1/rule/%s", a.url, ruleUID), nil)
|
||||
require.NoError(t, err)
|
||||
rule, status, raw := sendRequest[apimodels.GettableExtendedRuleNode](t, req, http.StatusOK)
|
||||
requireStatusCode(t, http.StatusOK, status, raw)
|
||||
return rule
|
||||
}
|
||||
|
||||
func sendRequest[T any](t *testing.T, req *http.Request, successStatusCode int) (T, int, string) {
|
||||
t.Helper()
|
||||
client := &http.Client{}
|
||||
|
||||
Reference in New Issue
Block a user