mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Expose updated_by in rules GET APIs (#99525)
--------- Signed-off-by: Yuri Tseretyan <yuriy.tseretyan@grafana.com>
This commit is contained in:
parent
32ae292334
commit
d71904cb27
@ -12,6 +12,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/api/routing"
|
"github.com/grafana/grafana/pkg/api/routing"
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
@ -43,11 +48,8 @@ import (
|
|||||||
secretsfakes "github.com/grafana/grafana/pkg/services/secrets/fakes"
|
secretsfakes "github.com/grafana/grafana/pkg/services/secrets/fakes"
|
||||||
secretskv "github.com/grafana/grafana/pkg/services/secrets/kvstore"
|
secretskv "github.com/grafana/grafana/pkg/services/secrets/kvstore"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
|
"github.com/grafana/grafana/pkg/services/user/usertest"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/mock"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_NoopServiceDoesNothing(t *testing.T) {
|
func Test_NoopServiceDoesNothing(t *testing.T) {
|
||||||
@ -874,7 +876,7 @@ func setUpServiceTest(t *testing.T, withDashboardMock bool, cfgOverrides ...conf
|
|||||||
cfg, featureToggles, nil, nil, rr, sqlStore, kvStore, nil, nil, quotatest.New(false, nil),
|
cfg, featureToggles, nil, nil, rr, sqlStore, kvStore, nil, nil, quotatest.New(false, nil),
|
||||||
secretsService, nil, alertMetrics, mockFolder, fakeAccessControl, dashboardService, nil, bus, fakeAccessControlService,
|
secretsService, nil, alertMetrics, mockFolder, fakeAccessControl, dashboardService, nil, bus, fakeAccessControlService,
|
||||||
annotationstest.NewFakeAnnotationsRepo(), &pluginstore.FakePluginStore{}, tracer, ruleStore,
|
annotationstest.NewFakeAnnotationsRepo(), &pluginstore.FakePluginStore{}, tracer, ruleStore,
|
||||||
httpclient.NewProvider(), ngalertfakes.NewFakeReceiverPermissionsService(),
|
httpclient.NewProvider(), ngalertfakes.NewFakeReceiverPermissionsService(), usertest.NewUserServiceFake(),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/ngalert/state"
|
"github.com/grafana/grafana/pkg/services/ngalert/state"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||||
"github.com/grafana/grafana/pkg/services/quota"
|
"github.com/grafana/grafana/pkg/services/quota"
|
||||||
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -78,6 +79,7 @@ type API struct {
|
|||||||
Historian Historian
|
Historian Historian
|
||||||
Tracer tracing.Tracer
|
Tracer tracing.Tracer
|
||||||
AppUrl *url.URL
|
AppUrl *url.URL
|
||||||
|
UserService user.Service
|
||||||
|
|
||||||
// Hooks can be used to replace API handlers for specific paths.
|
// Hooks can be used to replace API handlers for specific paths.
|
||||||
Hooks *Hooks
|
Hooks *Hooks
|
||||||
@ -135,6 +137,7 @@ func (api *API) RegisterAPIEndpoints(m *metrics.API) {
|
|||||||
amConfigStore: api.AlertingStore,
|
amConfigStore: api.AlertingStore,
|
||||||
amRefresher: api.MultiOrgAlertmanager,
|
amRefresher: api.MultiOrgAlertmanager,
|
||||||
featureManager: api.FeatureManager,
|
featureManager: api.FeatureManager,
|
||||||
|
userService: api.UserService,
|
||||||
},
|
},
|
||||||
), m)
|
), m)
|
||||||
api.RegisterTestingApiEndpoints(NewTestingApi(
|
api.RegisterTestingApiEndpoints(NewTestingApi(
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||||
"github.com/grafana/grafana/pkg/services/quota"
|
"github.com/grafana/grafana/pkg/services/quota"
|
||||||
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
@ -53,6 +54,7 @@ type RulerSrv struct {
|
|||||||
cfg *setting.UnifiedAlertingSettings
|
cfg *setting.UnifiedAlertingSettings
|
||||||
conditionValidator ConditionValidator
|
conditionValidator ConditionValidator
|
||||||
authz RuleAccessControlService
|
authz RuleAccessControlService
|
||||||
|
userService user.Service
|
||||||
|
|
||||||
amConfigStore AMConfigStore
|
amConfigStore AMConfigStore
|
||||||
amRefresher AMRefresher
|
amRefresher AMRefresher
|
||||||
@ -211,7 +213,7 @@ func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *contextmodel.ReqContext, nam
|
|||||||
result := apimodels.NamespaceConfigResponse{}
|
result := apimodels.NamespaceConfigResponse{}
|
||||||
|
|
||||||
for groupKey, rules := range ruleGroups {
|
for groupKey, rules := range ruleGroups {
|
||||||
result[namespace.Fullpath] = append(result[namespace.Fullpath], toGettableRuleGroupConfig(groupKey.RuleGroup, rules, provenanceRecords))
|
result[namespace.Fullpath] = append(result[namespace.Fullpath], toGettableRuleGroupConfig(groupKey.RuleGroup, rules, provenanceRecords, srv.resolveUserIdToNameFn(c.Req.Context())))
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.JSON(http.StatusAccepted, result)
|
return response.JSON(http.StatusAccepted, result)
|
||||||
@ -246,7 +248,7 @@ func (srv RulerSrv) RouteGetRulesGroupConfig(c *contextmodel.ReqContext, namespa
|
|||||||
|
|
||||||
result := apimodels.RuleGroupConfigResponse{
|
result := apimodels.RuleGroupConfigResponse{
|
||||||
// nolint:staticcheck
|
// nolint:staticcheck
|
||||||
GettableRuleGroupConfig: toGettableRuleGroupConfig(finalRuleGroup, rules, provenanceRecords),
|
GettableRuleGroupConfig: toGettableRuleGroupConfig(finalRuleGroup, rules, provenanceRecords, srv.resolveUserIdToNameFn(c.Req.Context())),
|
||||||
}
|
}
|
||||||
return response.JSON(http.StatusAccepted, result)
|
return response.JSON(http.StatusAccepted, result)
|
||||||
}
|
}
|
||||||
@ -300,7 +302,7 @@ func (srv RulerSrv) RouteGetRulesConfig(c *contextmodel.ReqContext) response.Res
|
|||||||
srv.log.Error("Namespace not visible to the user", "user", id, "userNamespace", userNamespace, "namespace", groupKey.NamespaceUID)
|
srv.log.Error("Namespace not visible to the user", "user", id, "userNamespace", userNamespace, "namespace", groupKey.NamespaceUID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result[folder.Fullpath] = append(result[folder.Fullpath], toGettableRuleGroupConfig(groupKey.RuleGroup, rules, provenanceRecords))
|
result[folder.Fullpath] = append(result[folder.Fullpath], toGettableRuleGroupConfig(groupKey.RuleGroup, rules, provenanceRecords, srv.resolveUserIdToNameFn(c.Req.Context())))
|
||||||
}
|
}
|
||||||
return response.JSON(http.StatusOK, result)
|
return response.JSON(http.StatusOK, result)
|
||||||
}
|
}
|
||||||
@ -323,7 +325,7 @@ func (srv RulerSrv) RouteGetRuleByUID(c *contextmodel.ReqContext, ruleUID string
|
|||||||
return response.ErrOrFallback(http.StatusInternalServerError, "failed to get rule provenance", err)
|
return response.ErrOrFallback(http.StatusInternalServerError, "failed to get rule provenance", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result := toGettableExtendedRuleNode(rule, map[string]ngmodels.Provenance{rule.ResourceID(): provenance})
|
result := toGettableExtendedRuleNode(rule, map[string]ngmodels.Provenance{rule.ResourceID(): provenance}, srv.resolveUserIdToNameFn(ctx))
|
||||||
|
|
||||||
return response.JSON(http.StatusOK, result)
|
return response.JSON(http.StatusOK, result)
|
||||||
}
|
}
|
||||||
@ -533,7 +535,7 @@ func changesToResponse(finalChanges *store.GroupDelta) response.Response {
|
|||||||
return response.JSON(http.StatusAccepted, body)
|
return response.JSON(http.StatusAccepted, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func toGettableRuleGroupConfig(groupName string, rules ngmodels.RulesGroup, provenanceRecords map[string]ngmodels.Provenance) apimodels.GettableRuleGroupConfig {
|
func toGettableRuleGroupConfig(groupName string, rules ngmodels.RulesGroup, provenanceRecords map[string]ngmodels.Provenance, userIdToName userIDToUserInfoFn) apimodels.GettableRuleGroupConfig {
|
||||||
rules.SortByGroupIndex()
|
rules.SortByGroupIndex()
|
||||||
ruleNodes := make([]apimodels.GettableExtendedRuleNode, 0, len(rules))
|
ruleNodes := make([]apimodels.GettableExtendedRuleNode, 0, len(rules))
|
||||||
var interval time.Duration
|
var interval time.Duration
|
||||||
@ -541,7 +543,7 @@ func toGettableRuleGroupConfig(groupName string, rules ngmodels.RulesGroup, prov
|
|||||||
interval = time.Duration(rules[0].IntervalSeconds) * time.Second
|
interval = time.Duration(rules[0].IntervalSeconds) * time.Second
|
||||||
}
|
}
|
||||||
for _, r := range rules {
|
for _, r := range rules {
|
||||||
ruleNodes = append(ruleNodes, toGettableExtendedRuleNode(*r, provenanceRecords))
|
ruleNodes = append(ruleNodes, toGettableExtendedRuleNode(*r, provenanceRecords, userIdToName))
|
||||||
}
|
}
|
||||||
return apimodels.GettableRuleGroupConfig{
|
return apimodels.GettableRuleGroupConfig{
|
||||||
Name: groupName,
|
Name: groupName,
|
||||||
@ -550,7 +552,7 @@ func toGettableRuleGroupConfig(groupName string, rules ngmodels.RulesGroup, prov
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toGettableExtendedRuleNode(r ngmodels.AlertRule, provenanceRecords map[string]ngmodels.Provenance) apimodels.GettableExtendedRuleNode {
|
func toGettableExtendedRuleNode(r ngmodels.AlertRule, provenanceRecords map[string]ngmodels.Provenance, userIdToName userIDToUserInfoFn) apimodels.GettableExtendedRuleNode {
|
||||||
provenance := ngmodels.ProvenanceNone
|
provenance := ngmodels.ProvenanceNone
|
||||||
if prov, exists := provenanceRecords[r.ResourceID()]; exists {
|
if prov, exists := provenanceRecords[r.ResourceID()]; exists {
|
||||||
provenance = prov
|
provenance = prov
|
||||||
@ -564,6 +566,7 @@ func toGettableExtendedRuleNode(r ngmodels.AlertRule, provenanceRecords map[stri
|
|||||||
Condition: r.Condition,
|
Condition: r.Condition,
|
||||||
Data: ApiAlertQueriesFromAlertQueries(r.Data),
|
Data: ApiAlertQueriesFromAlertQueries(r.Data),
|
||||||
Updated: r.Updated,
|
Updated: r.Updated,
|
||||||
|
UpdatedBy: userIdToName(r.UpdatedBy),
|
||||||
IntervalSeconds: r.IntervalSeconds,
|
IntervalSeconds: r.IntervalSeconds,
|
||||||
Version: r.Version,
|
Version: r.Version,
|
||||||
UID: r.UID,
|
UID: r.UID,
|
||||||
@ -724,3 +727,28 @@ func (srv RulerSrv) searchAuthorizedAlertRules(ctx context.Context, q authorized
|
|||||||
}
|
}
|
||||||
return byGroupKey, totalGroups, nil
|
return byGroupKey, totalGroups, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type userIDToUserInfoFn func(id *ngmodels.UserUID) *apimodels.UserInfo
|
||||||
|
|
||||||
|
// getIdentityName returns name of either user or service account
|
||||||
|
func (srv RulerSrv) resolveUserIdToNameFn(ctx context.Context) userIDToUserInfoFn {
|
||||||
|
return func(id *ngmodels.UserUID) *apimodels.UserInfo {
|
||||||
|
if id == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
u, err := srv.userService.GetByUID(ctx, &user.GetUserByUIDQuery{
|
||||||
|
UID: string(*id),
|
||||||
|
})
|
||||||
|
var result string
|
||||||
|
if err != nil {
|
||||||
|
srv.log.FromContext(ctx).Warn("Failed to get user by uid. Defaulting to an empty name", "uid", id, "error", err)
|
||||||
|
}
|
||||||
|
if u != nil {
|
||||||
|
result = u.NameOrFallback()
|
||||||
|
}
|
||||||
|
return &apimodels.UserInfo{
|
||||||
|
UID: string(*id),
|
||||||
|
Name: result,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -32,7 +32,9 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
|
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
|
"github.com/grafana/grafana/pkg/services/user/usertest"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
"github.com/grafana/grafana/pkg/util"
|
||||||
"github.com/grafana/grafana/pkg/util/cmputil"
|
"github.com/grafana/grafana/pkg/util/cmputil"
|
||||||
"github.com/grafana/grafana/pkg/web"
|
"github.com/grafana/grafana/pkg/web"
|
||||||
)
|
)
|
||||||
@ -357,6 +359,72 @@ func TestRouteGetRuleByUID(t *testing.T) {
|
|||||||
require.Equal(t, expectedRule.Title, result.GrafanaManagedAlert.Title)
|
require.Equal(t, expectedRule.Title, result.GrafanaManagedAlert.Title)
|
||||||
require.True(t, result.GrafanaManagedAlert.Metadata.EditorSettings.SimplifiedQueryAndExpressionsSection)
|
require.True(t, result.GrafanaManagedAlert.Metadata.EditorSettings.SimplifiedQueryAndExpressionsSection)
|
||||||
require.True(t, result.GrafanaManagedAlert.Metadata.EditorSettings.SimplifiedNotificationsSection)
|
require.True(t, result.GrafanaManagedAlert.Metadata.EditorSettings.SimplifiedNotificationsSection)
|
||||||
|
|
||||||
|
t.Run("should resolve Updated_by with user service", func(t *testing.T) {
|
||||||
|
testcases := []struct {
|
||||||
|
desc string
|
||||||
|
UpdatedBy *models.UserUID
|
||||||
|
User *user.User
|
||||||
|
UserServiceError error
|
||||||
|
Expected *apimodels.UserInfo
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "nil if UpdatedBy is nil",
|
||||||
|
UpdatedBy: nil,
|
||||||
|
User: nil,
|
||||||
|
UserServiceError: nil,
|
||||||
|
Expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "just UID if user is not found",
|
||||||
|
UpdatedBy: util.Pointer(models.UserUID("test-uid")),
|
||||||
|
User: nil,
|
||||||
|
UserServiceError: nil,
|
||||||
|
Expected: &apimodels.UserInfo{
|
||||||
|
UID: "test-uid",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "just UID if error",
|
||||||
|
UpdatedBy: util.Pointer(models.UserUID("test-uid")),
|
||||||
|
UserServiceError: errors.New("error"),
|
||||||
|
Expected: &apimodels.UserInfo{
|
||||||
|
UID: "test-uid",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "login if it's known user",
|
||||||
|
UpdatedBy: util.Pointer(models.UserUID("test-uid")),
|
||||||
|
User: &user.User{
|
||||||
|
Login: "Test",
|
||||||
|
},
|
||||||
|
UserServiceError: nil,
|
||||||
|
Expected: &apimodels.UserInfo{
|
||||||
|
UID: "test-uid",
|
||||||
|
Name: "Test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testcases {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
expectedRule.UpdatedBy = tc.UpdatedBy
|
||||||
|
svc := createService(ruleStore)
|
||||||
|
usvc := usertest.NewUserServiceFake()
|
||||||
|
usvc.ExpectedUser = tc.User
|
||||||
|
usvc.ExpectedError = tc.UserServiceError
|
||||||
|
svc.userService = usvc
|
||||||
|
|
||||||
|
response := svc.RouteGetRuleByUID(req, expectedRule.UID)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, response.Status())
|
||||||
|
result := &apimodels.GettableExtendedRuleNode{}
|
||||||
|
require.NoError(t, json.Unmarshal(response.Body(), result))
|
||||||
|
require.NotNil(t, result)
|
||||||
|
|
||||||
|
require.Equal(t, tc.Expected, result.GrafanaManagedAlert.UpdatedBy)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("error when fetching rule with non-existent UID", func(t *testing.T) {
|
t.Run("error when fetching rule with non-existent UID", func(t *testing.T) {
|
||||||
@ -659,6 +727,7 @@ func createService(store *fakes.RuleStore) *RulerSrv {
|
|||||||
amConfigStore: &fakeAMRefresher{},
|
amConfigStore: &fakeAMRefresher{},
|
||||||
amRefresher: &fakeAMRefresher{},
|
amRefresher: &fakeAMRefresher{},
|
||||||
featureManager: featuremgmt.WithFeatures(featuremgmt.FlagGrafanaManagedRecordingRules),
|
featureManager: featuremgmt.WithFeatures(featuremgmt.FlagGrafanaManagedRecordingRules),
|
||||||
|
userService: usertest.NewUserServiceFake(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,7 +364,7 @@ const (
|
|||||||
type PostableExtendedRuleNode struct {
|
type PostableExtendedRuleNode struct {
|
||||||
// note: this works with yaml v3 but not v2 (the inline tag isn't accepted on pointers in v2)
|
// note: this works with yaml v3 but not v2 (the inline tag isn't accepted on pointers in v2)
|
||||||
*ApiRuleNode `yaml:",inline"`
|
*ApiRuleNode `yaml:",inline"`
|
||||||
//GrafanaManagedAlert yaml.Node `yaml:"grafana_alert,omitempty"`
|
// GrafanaManagedAlert yaml.Node `yaml:"grafana_alert,omitempty"`
|
||||||
GrafanaManagedAlert *PostableGrafanaRule `yaml:"grafana_alert,omitempty" json:"grafana_alert,omitempty"`
|
GrafanaManagedAlert *PostableGrafanaRule `yaml:"grafana_alert,omitempty" json:"grafana_alert,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -401,7 +401,7 @@ func (n *PostableExtendedRuleNode) validate() error {
|
|||||||
type GettableExtendedRuleNode struct {
|
type GettableExtendedRuleNode struct {
|
||||||
// note: this works with yaml v3 but not v2 (the inline tag isn't accepted on pointers in v2)
|
// note: this works with yaml v3 but not v2 (the inline tag isn't accepted on pointers in v2)
|
||||||
*ApiRuleNode `yaml:",inline"`
|
*ApiRuleNode `yaml:",inline"`
|
||||||
//GrafanaManagedAlert yaml.Node `yaml:"grafana_alert,omitempty"`
|
// GrafanaManagedAlert yaml.Node `yaml:"grafana_alert,omitempty"`
|
||||||
GrafanaManagedAlert *GettableGrafanaRule `yaml:"grafana_alert,omitempty" json:"grafana_alert,omitempty"`
|
GrafanaManagedAlert *GettableGrafanaRule `yaml:"grafana_alert,omitempty" json:"grafana_alert,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -541,6 +541,7 @@ type GettableGrafanaRule struct {
|
|||||||
Condition string `json:"condition" yaml:"condition"`
|
Condition string `json:"condition" yaml:"condition"`
|
||||||
Data []AlertQuery `json:"data" yaml:"data"`
|
Data []AlertQuery `json:"data" yaml:"data"`
|
||||||
Updated time.Time `json:"updated" yaml:"updated"`
|
Updated time.Time `json:"updated" yaml:"updated"`
|
||||||
|
UpdatedBy *UserInfo `json:"updated_by" yaml:"updated_by"`
|
||||||
IntervalSeconds int64 `json:"intervalSeconds" yaml:"intervalSeconds"`
|
IntervalSeconds int64 `json:"intervalSeconds" yaml:"intervalSeconds"`
|
||||||
Version int64 `json:"version" yaml:"version"`
|
Version int64 `json:"version" yaml:"version"`
|
||||||
UID string `json:"uid" yaml:"uid"`
|
UID string `json:"uid" yaml:"uid"`
|
||||||
@ -555,6 +556,12 @@ type GettableGrafanaRule struct {
|
|||||||
Metadata *AlertRuleMetadata `json:"metadata,omitempty" yaml:"metadata,omitempty"`
|
Metadata *AlertRuleMetadata `json:"metadata,omitempty" yaml:"metadata,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserInfo represents user-related information, including a unique identifier and a name.
|
||||||
|
type UserInfo struct {
|
||||||
|
UID string `json:"uid"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
// AlertQuery represents a single query associated with an alert definition.
|
// AlertQuery represents a single query associated with an alert definition.
|
||||||
type AlertQuery struct {
|
type AlertQuery struct {
|
||||||
// RefID is the unique identifier of the query, set by the frontend call.
|
// RefID is the unique identifier of the query, set by the frontend call.
|
||||||
|
@ -49,6 +49,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/quota"
|
"github.com/grafana/grafana/pkg/services/quota"
|
||||||
"github.com/grafana/grafana/pkg/services/rendering"
|
"github.com/grafana/grafana/pkg/services/rendering"
|
||||||
"github.com/grafana/grafana/pkg/services/secrets"
|
"github.com/grafana/grafana/pkg/services/secrets"
|
||||||
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -78,6 +79,7 @@ func ProvideService(
|
|||||||
ruleStore *store.DBstore,
|
ruleStore *store.DBstore,
|
||||||
httpClientProvider httpclient.Provider,
|
httpClientProvider httpclient.Provider,
|
||||||
resourcePermissions accesscontrol.ReceiverPermissionsService,
|
resourcePermissions accesscontrol.ReceiverPermissionsService,
|
||||||
|
userService user.Service,
|
||||||
) (*AlertNG, error) {
|
) (*AlertNG, error) {
|
||||||
ng := &AlertNG{
|
ng := &AlertNG{
|
||||||
Cfg: cfg,
|
Cfg: cfg,
|
||||||
@ -106,6 +108,7 @@ func ProvideService(
|
|||||||
store: ruleStore,
|
store: ruleStore,
|
||||||
httpClientProvider: httpClientProvider,
|
httpClientProvider: httpClientProvider,
|
||||||
ResourcePermissions: resourcePermissions,
|
ResourcePermissions: resourcePermissions,
|
||||||
|
userService: userService,
|
||||||
}
|
}
|
||||||
|
|
||||||
if ng.IsDisabled() {
|
if ng.IsDisabled() {
|
||||||
@ -154,6 +157,7 @@ type AlertNG struct {
|
|||||||
ResourcePermissions accesscontrol.ReceiverPermissionsService
|
ResourcePermissions accesscontrol.ReceiverPermissionsService
|
||||||
annotationsRepo annotations.Repository
|
annotationsRepo annotations.Repository
|
||||||
store *store.DBstore
|
store *store.DBstore
|
||||||
|
userService user.Service
|
||||||
|
|
||||||
bus bus.Bus
|
bus bus.Bus
|
||||||
pluginsStore pluginstore.Store
|
pluginsStore pluginstore.Store
|
||||||
@ -496,6 +500,7 @@ func (ng *AlertNG) init() error {
|
|||||||
Historian: history,
|
Historian: history,
|
||||||
Hooks: api.NewHooks(ng.Log),
|
Hooks: api.NewHooks(ng.Log),
|
||||||
Tracer: ng.tracer,
|
Tracer: ng.tracer,
|
||||||
|
UserService: ng.userService,
|
||||||
}
|
}
|
||||||
ng.Api.RegisterAPIEndpoints(ng.Metrics.GetAPIMetrics())
|
ng.Api.RegisterAPIEndpoints(ng.Metrics.GetAPIMetrics())
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/secrets/database"
|
"github.com/grafana/grafana/pkg/services/secrets/database"
|
||||||
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
|
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
|
"github.com/grafana/grafana/pkg/services/user/usertest"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
@ -90,7 +91,7 @@ func SetupTestEnv(tb testing.TB, baseInterval time.Duration, opts ...TestEnvOpti
|
|||||||
ng, err := ngalert.ProvideService(
|
ng, err := ngalert.ProvideService(
|
||||||
cfg, options.featureToggles, nil, nil, routing.NewRouteRegister(), sqlStore, kvstore.NewFakeKVStore(), nil, nil, quotatest.New(false, nil),
|
cfg, options.featureToggles, nil, nil, routing.NewRouteRegister(), sqlStore, kvstore.NewFakeKVStore(), nil, nil, quotatest.New(false, nil),
|
||||||
secretsService, nil, m, folderService, ac, &dashboards.FakeDashboardService{}, nil, bus, ac,
|
secretsService, nil, m, folderService, ac, &dashboards.FakeDashboardService{}, nil, bus, ac,
|
||||||
annotationstest.NewFakeAnnotationsRepo(), &pluginstore.FakePluginStore{}, tracer, ruleStore, httpclient.NewProvider(), ngalertfakes.NewFakeReceiverPermissionsService(),
|
annotationstest.NewFakeAnnotationsRepo(), &pluginstore.FakePluginStore{}, tracer, ruleStore, httpclient.NewProvider(), ngalertfakes.NewFakeReceiverPermissionsService(), usertest.NewUserServiceFake(),
|
||||||
)
|
)
|
||||||
require.NoError(tb, err)
|
require.NoError(tb, err)
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
"github.com/grafana/grafana/pkg/services/user/userimpl"
|
"github.com/grafana/grafana/pkg/services/user/userimpl"
|
||||||
|
"github.com/grafana/grafana/pkg/services/user/usertest"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
@ -512,7 +513,7 @@ func setupEnv(t *testing.T, sqlStore db.DB, cfg *setting.Cfg, b bus.Bus, quotaSe
|
|||||||
_, err = ngalert.ProvideService(
|
_, err = ngalert.ProvideService(
|
||||||
cfg, featuremgmt.WithFeatures(), nil, nil, routing.NewRouteRegister(), sqlStore, ngalertfakes.NewFakeKVStore(t), nil, nil, quotaService,
|
cfg, featuremgmt.WithFeatures(), nil, nil, routing.NewRouteRegister(), sqlStore, ngalertfakes.NewFakeKVStore(t), nil, nil, quotaService,
|
||||||
secretsService, nil, m, &foldertest.FakeService{}, &acmock.Mock{}, &dashboards.FakeDashboardService{}, nil, b, &acmock.Mock{},
|
secretsService, nil, m, &foldertest.FakeService{}, &acmock.Mock{}, &dashboards.FakeDashboardService{}, nil, b, &acmock.Mock{},
|
||||||
annotationstest.NewFakeAnnotationsRepo(), &pluginstore.FakePluginStore{}, tracer, ruleStore, httpclient.NewProvider(), ngalertfakes.NewFakeReceiverPermissionsService(),
|
annotationstest.NewFakeAnnotationsRepo(), &pluginstore.FakePluginStore{}, tracer, ruleStore, httpclient.NewProvider(), ngalertfakes.NewFakeReceiverPermissionsService(), usertest.NewUserServiceFake(),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
_, err = storesrv.ProvideService(sqlStore, featuremgmt.WithFeatures(), cfg, quotaService, storesrv.ProvideSystemUsersService())
|
_, err = storesrv.ProvideService(sqlStore, featuremgmt.WithFeatures(), cfg, quotaService, storesrv.ProvideSystemUsersService())
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@ -121,6 +120,7 @@ func TestIntegrationAlertRulePermissions(t *testing.T) {
|
|||||||
|
|
||||||
pathsToIgnore := []string{
|
pathsToIgnore := []string{
|
||||||
"GrafanaManagedAlert.Updated",
|
"GrafanaManagedAlert.Updated",
|
||||||
|
"GrafanaManagedAlert.UpdatedBy",
|
||||||
"GrafanaManagedAlert.UID",
|
"GrafanaManagedAlert.UID",
|
||||||
"GrafanaManagedAlert.ID",
|
"GrafanaManagedAlert.ID",
|
||||||
"GrafanaManagedAlert.Data.Model",
|
"GrafanaManagedAlert.Data.Model",
|
||||||
@ -422,6 +422,7 @@ func TestIntegrationAlertRuleNestedPermissions(t *testing.T) {
|
|||||||
|
|
||||||
pathsToIgnore := []string{
|
pathsToIgnore := []string{
|
||||||
"GrafanaManagedAlert.Updated",
|
"GrafanaManagedAlert.Updated",
|
||||||
|
"GrafanaManagedAlert.UpdatedBy",
|
||||||
"GrafanaManagedAlert.UID",
|
"GrafanaManagedAlert.UID",
|
||||||
"GrafanaManagedAlert.ID",
|
"GrafanaManagedAlert.ID",
|
||||||
"GrafanaManagedAlert.Data.Model",
|
"GrafanaManagedAlert.Data.Model",
|
||||||
@ -1144,6 +1145,10 @@ func TestIntegrationRulerRulesFilterByDashboard(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
"updated": "2021-02-21T01:10:30Z",
|
"updated": "2021-02-21T01:10:30Z",
|
||||||
|
"updated_by": {
|
||||||
|
"uid": "uid",
|
||||||
|
"name": "grafana"
|
||||||
|
},
|
||||||
"intervalSeconds": 60,
|
"intervalSeconds": 60,
|
||||||
"is_paused": false,
|
"is_paused": false,
|
||||||
"version": 1,
|
"version": 1,
|
||||||
@ -1183,6 +1188,10 @@ func TestIntegrationRulerRulesFilterByDashboard(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
"updated": "2021-02-21T01:10:30Z",
|
"updated": "2021-02-21T01:10:30Z",
|
||||||
|
"updated_by": {
|
||||||
|
"uid": "uid",
|
||||||
|
"name": "grafana"
|
||||||
|
},
|
||||||
"intervalSeconds": 60,
|
"intervalSeconds": 60,
|
||||||
"is_paused": false,
|
"is_paused": false,
|
||||||
"version": 1,
|
"version": 1,
|
||||||
@ -1234,6 +1243,10 @@ func TestIntegrationRulerRulesFilterByDashboard(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
"updated": "2021-02-21T01:10:30Z",
|
"updated": "2021-02-21T01:10:30Z",
|
||||||
|
"updated_by": {
|
||||||
|
"uid": "uid",
|
||||||
|
"name": "grafana"
|
||||||
|
},
|
||||||
"intervalSeconds": 60,
|
"intervalSeconds": 60,
|
||||||
"is_paused": false,
|
"is_paused": false,
|
||||||
"version": 1,
|
"version": 1,
|
||||||
@ -1498,8 +1511,9 @@ func TestIntegrationRuleCreate(t *testing.T) {
|
|||||||
client.CreateFolder(t, namespaceUID, namespaceUID)
|
client.CreateFolder(t, namespaceUID, namespaceUID)
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
config apimodels.PostableRuleGroupConfig
|
config apimodels.PostableRuleGroupConfig
|
||||||
|
expected apimodels.GettableRuleGroupConfig
|
||||||
}{{
|
}{{
|
||||||
name: "can create a rule with UTF-8",
|
name: "can create a rule with UTF-8",
|
||||||
config: apimodels.PostableRuleGroupConfig{
|
config: apimodels.PostableRuleGroupConfig{
|
||||||
@ -1514,8 +1528,7 @@ func TestIntegrationRuleCreate(t *testing.T) {
|
|||||||
"_bar1": "baz🙂",
|
"_bar1": "baz🙂",
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"Προμηθέας": "prom", // Prometheus in Greek
|
"Προμηθέας": "prom", // Prometheus in Greek
|
||||||
"犬": "Shiba Inu", // Dog in Japanese
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
||||||
@ -1536,6 +1549,52 @@ func TestIntegrationRuleCreate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
expected: apimodels.GettableRuleGroupConfig{
|
||||||
|
Name: "test1",
|
||||||
|
Interval: model.Duration(time.Minute),
|
||||||
|
Rules: []apimodels.GettableExtendedRuleNode{
|
||||||
|
{
|
||||||
|
ApiRuleNode: &apimodels.ApiRuleNode{
|
||||||
|
For: util.Pointer(model.Duration(2 * time.Minute)),
|
||||||
|
Labels: map[string]string{
|
||||||
|
"foo🙂": "bar",
|
||||||
|
"_bar1": "baz🙂",
|
||||||
|
},
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"Προμηθέας": "prom", // Prometheus in Greek
|
||||||
|
},
|
||||||
|
},
|
||||||
|
GrafanaManagedAlert: &apimodels.GettableGrafanaRule{
|
||||||
|
OrgID: 1,
|
||||||
|
Title: "test1 rule1",
|
||||||
|
Condition: "A",
|
||||||
|
Data: []apimodels.AlertQuery{
|
||||||
|
{
|
||||||
|
RefID: "A",
|
||||||
|
RelativeTimeRange: apimodels.RelativeTimeRange{
|
||||||
|
From: apimodels.Duration(0),
|
||||||
|
To: apimodels.Duration(15 * time.Minute),
|
||||||
|
},
|
||||||
|
DatasourceUID: expr.DatasourceUID,
|
||||||
|
Model: json.RawMessage(`{"expression":"1","intervalMs":1000,"maxDataPoints":43200,"type":"math"}`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UpdatedBy: &apimodels.UserInfo{
|
||||||
|
Name: "admin",
|
||||||
|
},
|
||||||
|
IntervalSeconds: 60,
|
||||||
|
Version: 1,
|
||||||
|
NamespaceUID: namespaceUID,
|
||||||
|
RuleGroup: "test1",
|
||||||
|
NoDataState: "NoData",
|
||||||
|
ExecErrState: "Alerting",
|
||||||
|
Provenance: "",
|
||||||
|
IsPaused: false,
|
||||||
|
Metadata: &apimodels.AlertRuleMetadata{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
@ -1545,6 +1604,27 @@ func TestIntegrationRuleCreate(t *testing.T) {
|
|||||||
require.Len(t, resp.Created, 1)
|
require.Len(t, resp.Created, 1)
|
||||||
require.Len(t, resp.Updated, 0)
|
require.Len(t, resp.Updated, 0)
|
||||||
require.Len(t, resp.Deleted, 0)
|
require.Len(t, resp.Deleted, 0)
|
||||||
|
got, _, _ := client.GetRulesGroupWithStatus(t, namespaceUID, tc.config.Name)
|
||||||
|
|
||||||
|
pathsToIgnore := []string{
|
||||||
|
"GrafanaManagedAlert.Updated",
|
||||||
|
"GrafanaManagedAlert.UpdatedBy.UID",
|
||||||
|
"GrafanaManagedAlert.UID",
|
||||||
|
"GrafanaManagedAlert.ID",
|
||||||
|
"GrafanaManagedAlert.NamespaceID",
|
||||||
|
}
|
||||||
|
|
||||||
|
// compare expected and actual and ignore the dynamic fields
|
||||||
|
diff := cmp.Diff(tc.expected, got.GettableRuleGroupConfig, cmp.FilterPath(func(path cmp.Path) bool {
|
||||||
|
for _, s := range pathsToIgnore {
|
||||||
|
if strings.HasSuffix(path.String(), s) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}, cmp.Ignore()))
|
||||||
|
|
||||||
|
require.Empty(t, diff)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1696,6 +1776,18 @@ func TestIntegrationRuleUpdate(t *testing.T) {
|
|||||||
require.Equalf(t, http.StatusAccepted, status, "failed to post noop rule group. Response: %s", body)
|
require.Equalf(t, http.StatusAccepted, status, "failed to post noop rule group. Response: %s", body)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
t.Run("should set updated_by", func(t *testing.T) {
|
||||||
|
group := generateAlertRuleGroup(1, alertRuleGen())
|
||||||
|
expected := model.Duration(10 * time.Second)
|
||||||
|
group.Rules[0].ApiRuleNode.For = &expected
|
||||||
|
|
||||||
|
_, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group)
|
||||||
|
require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body)
|
||||||
|
getGroup := client.GetRulesGroup(t, folderUID, group.Name)
|
||||||
|
require.NotNil(t, getGroup.Rules[0].GrafanaManagedAlert.UpdatedBy)
|
||||||
|
assert.NotEmpty(t, getGroup.Rules[0].GrafanaManagedAlert.UpdatedBy.UID)
|
||||||
|
assert.Equal(t, "grafana", getGroup.Rules[0].GrafanaManagedAlert.UpdatedBy.Name)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationAlertAndGroupsQuery(t *testing.T) {
|
func TestIntegrationAlertAndGroupsQuery(t *testing.T) {
|
||||||
@ -2438,6 +2530,10 @@ func TestIntegrationQuota(t *testing.T) {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated":"2021-02-21T01:10:30Z",
|
"updated":"2021-02-21T01:10:30Z",
|
||||||
|
"updated_by": {
|
||||||
|
"uid": "uid",
|
||||||
|
"name": "grafana"
|
||||||
|
},
|
||||||
"intervalSeconds":60,
|
"intervalSeconds":60,
|
||||||
"is_paused": false,
|
"is_paused": false,
|
||||||
"version":2,
|
"version":2,
|
||||||
@ -2510,13 +2606,8 @@ func TestIntegrationDeleteFolderWithRules(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 200, resp.StatusCode)
|
assert.Equal(t, 200, resp.StatusCode)
|
||||||
|
body, _ := rulesNamespaceWithoutVariableValues(t, b)
|
||||||
re := regexp.MustCompile(`"uid":"([\w|-]+)"`)
|
expectedGetRulesResponseBody := `{
|
||||||
b = re.ReplaceAll(b, []byte(`"uid":""`))
|
|
||||||
re = regexp.MustCompile(`"updated":"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z)"`)
|
|
||||||
b = re.ReplaceAll(b, []byte(`"updated":"2021-05-19T19:47:55Z"`))
|
|
||||||
|
|
||||||
expectedGetRulesResponseBody := fmt.Sprintf(`{
|
|
||||||
"default": [
|
"default": [
|
||||||
{
|
{
|
||||||
"name": "arulegroup",
|
"name": "arulegroup",
|
||||||
@ -2553,12 +2644,16 @@ func TestIntegrationDeleteFolderWithRules(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated": "2021-05-19T19:47:55Z",
|
"updated": "2021-02-21T01:10:30Z",
|
||||||
|
"updated_by" : {
|
||||||
|
"uid": "uid",
|
||||||
|
"name": "editor"
|
||||||
|
},
|
||||||
"intervalSeconds": 60,
|
"intervalSeconds": 60,
|
||||||
"is_paused": false,
|
"is_paused": false,
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"uid": "",
|
"uid": "uid",
|
||||||
"namespace_uid": %q,
|
"namespace_uid": "nsuid",
|
||||||
"rule_group": "arulegroup",
|
"rule_group": "arulegroup",
|
||||||
"no_data_state": "NoData",
|
"no_data_state": "NoData",
|
||||||
"exec_err_state": "Alerting",
|
"exec_err_state": "Alerting",
|
||||||
@ -2573,8 +2668,8 @@ func TestIntegrationDeleteFolderWithRules(t *testing.T) {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}`, namespaceUID)
|
}`
|
||||||
assert.JSONEq(t, expectedGetRulesResponseBody, string(b))
|
assert.JSONEq(t, expectedGetRulesResponseBody, body)
|
||||||
})
|
})
|
||||||
t.Run("editor can not delete the folder because it contains Grafana 8 alerts", func(t *testing.T) {
|
t.Run("editor can not delete the folder because it contains Grafana 8 alerts", func(t *testing.T) {
|
||||||
u := fmt.Sprintf("http://editor:editor@%s/api/folders/%s", grafanaListedAddr, namespaceUID)
|
u := fmt.Sprintf("http://editor:editor@%s/api/folders/%s", grafanaListedAddr, namespaceUID)
|
||||||
@ -3033,6 +3128,10 @@ func TestIntegrationAlertRuleCRUD(t *testing.T) {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated":"2021-02-21T01:10:30Z",
|
"updated":"2021-02-21T01:10:30Z",
|
||||||
|
"updated_by": {
|
||||||
|
"uid": "uid",
|
||||||
|
"name": "grafana"
|
||||||
|
},
|
||||||
"intervalSeconds":60,
|
"intervalSeconds":60,
|
||||||
"is_paused": false,
|
"is_paused": false,
|
||||||
"version":1,
|
"version":1,
|
||||||
@ -3075,6 +3174,10 @@ func TestIntegrationAlertRuleCRUD(t *testing.T) {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated":"2021-02-21T01:10:30Z",
|
"updated":"2021-02-21T01:10:30Z",
|
||||||
|
"updated_by": {
|
||||||
|
"uid": "uid",
|
||||||
|
"name": "grafana"
|
||||||
|
},
|
||||||
"intervalSeconds":60,
|
"intervalSeconds":60,
|
||||||
"is_paused": false,
|
"is_paused": false,
|
||||||
"version":1,
|
"version":1,
|
||||||
@ -3389,6 +3492,10 @@ func TestIntegrationAlertRuleCRUD(t *testing.T) {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated":"2021-02-21T01:10:30Z",
|
"updated":"2021-02-21T01:10:30Z",
|
||||||
|
"updated_by": {
|
||||||
|
"uid": "uid",
|
||||||
|
"name": "grafana"
|
||||||
|
},
|
||||||
"intervalSeconds":60,
|
"intervalSeconds":60,
|
||||||
"is_paused": false,
|
"is_paused": false,
|
||||||
"version":2,
|
"version":2,
|
||||||
@ -3504,6 +3611,10 @@ func TestIntegrationAlertRuleCRUD(t *testing.T) {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated":"2021-02-21T01:10:30Z",
|
"updated":"2021-02-21T01:10:30Z",
|
||||||
|
"updated_by": {
|
||||||
|
"uid": "uid",
|
||||||
|
"name": "grafana"
|
||||||
|
},
|
||||||
"intervalSeconds":60,
|
"intervalSeconds":60,
|
||||||
"is_paused":false,
|
"is_paused":false,
|
||||||
"version":3,
|
"version":3,
|
||||||
@ -3598,6 +3709,10 @@ func TestIntegrationAlertRuleCRUD(t *testing.T) {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated":"2021-02-21T01:10:30Z",
|
"updated":"2021-02-21T01:10:30Z",
|
||||||
|
"updated_by": {
|
||||||
|
"uid": "uid",
|
||||||
|
"name": "grafana"
|
||||||
|
},
|
||||||
"intervalSeconds":60,
|
"intervalSeconds":60,
|
||||||
"is_paused":false,
|
"is_paused":false,
|
||||||
"version":3,
|
"version":3,
|
||||||
@ -4289,6 +4404,7 @@ func rulesNamespaceWithoutVariableValues(t *testing.T, b []byte) (string, map[st
|
|||||||
rule.GrafanaManagedAlert.UID = "uid"
|
rule.GrafanaManagedAlert.UID = "uid"
|
||||||
rule.GrafanaManagedAlert.NamespaceUID = "nsuid"
|
rule.GrafanaManagedAlert.NamespaceUID = "nsuid"
|
||||||
rule.GrafanaManagedAlert.Updated = time.Date(2021, time.Month(2), 21, 1, 10, 30, 0, time.UTC)
|
rule.GrafanaManagedAlert.Updated = time.Date(2021, time.Month(2), 21, 1, 10, 30, 0, time.UTC)
|
||||||
|
rule.GrafanaManagedAlert.UpdatedBy.UID = "uid"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user