mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Export and provisioning rules into subfolders (#77450)
* Folders: Optionally include fullpath in service responses * Alerting: Export folder fullpath instead of title * Escape separator in folder title * Add support for provisiong alret rules into subfolders * Use FolderService for creating folders during provisioning * Export WithFullpath() folder service function --------- Co-authored-by: Tania B <yalyna.ts@gmail.com> Co-authored-by: Yuri Tseretyan <yuriy.tseretyan@grafana.com>
This commit is contained in:
parent
e1aedb65b3
commit
17ca61d7f8
@ -333,7 +333,6 @@ func (dr *DashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dt
|
||||
|
||||
func (dr *DashboardServiceImpl) SaveFolderForProvisionedDashboards(ctx context.Context, dto *folder.CreateFolderCommand) (*folder.Folder, error) {
|
||||
dto.SignedInUser = accesscontrol.BackgroundUser("dashboard_provisioning", dto.OrgID, org.RoleAdmin, provisionerPermissions)
|
||||
|
||||
f, err := dr.folderService.Create(ctx, dto)
|
||||
if err != nil {
|
||||
dr.log.Error("failed to create folder for provisioned dashboards", "folder", dto.Title, "org", dto.OrgID, "err", err)
|
||||
|
@ -98,6 +98,8 @@ func (d *DashboardFolderStoreImpl) GetFolderByUID(ctx context.Context, orgID int
|
||||
return dashboards.FromDashboard(&dashboard), nil
|
||||
}
|
||||
|
||||
// GetFolders returns all folders for the given orgID and UIDs.
|
||||
// If no UIDs are provided then all folders for the org are returned.
|
||||
func (d *DashboardFolderStoreImpl) GetFolders(ctx context.Context, orgID int64, uids []string) (map[string]*folder.Folder, error) {
|
||||
m := make(map[string]*folder.Folder, len(uids))
|
||||
if len(uids) == 0 {
|
||||
|
@ -34,6 +34,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
const FULLPATH_SEPARATOR = "/"
|
||||
|
||||
type Service struct {
|
||||
store store
|
||||
db db.DB
|
||||
@ -1109,6 +1111,36 @@ func (s *Service) buildSaveDashboardCommand(ctx context.Context, dto *dashboards
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// SplitFullpath splits a string into an array of strings using the FULLPATH_SEPARATOR as the delimiter.
|
||||
// It handles escape characters by appending the separator and the new string if the current string ends with an escape character.
|
||||
// The resulting array does not contain empty strings.
|
||||
func SplitFullpath(s string) []string {
|
||||
splitStrings := strings.Split(s, FULLPATH_SEPARATOR)
|
||||
|
||||
result := make([]string, 0)
|
||||
current := ""
|
||||
|
||||
for _, str := range splitStrings {
|
||||
if strings.HasSuffix(current, "\\") {
|
||||
// If the current string ends with an escape character, append the separator and the new string
|
||||
current = current[:len(current)-1] + FULLPATH_SEPARATOR + str
|
||||
} else {
|
||||
// If the current string does not end with an escape character, append the current string to the result and start a new current string
|
||||
if current != "" {
|
||||
result = append(result, current)
|
||||
}
|
||||
current = str
|
||||
}
|
||||
}
|
||||
|
||||
// Append the last string to the result
|
||||
if current != "" {
|
||||
result = append(result, current)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// getGuardianForSavePermissionCheck returns the guardian to be used for checking permission of dashboard
|
||||
// It replaces deleted Dashboard.GetDashboardIdForSavePermissionCheck()
|
||||
func getGuardianForSavePermissionCheck(ctx context.Context, d *dashboards.Dashboard, user identity.Requester) (guardian.DashboardGuardian, error) {
|
||||
|
@ -2216,3 +2216,54 @@ func createRule(t *testing.T, store *ngstore.DBstore, folderUID, title string) *
|
||||
|
||||
return &rule
|
||||
}
|
||||
|
||||
func TestSplitFullpath(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "empty string",
|
||||
input: "",
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "root folder",
|
||||
input: "/",
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "single folder",
|
||||
input: "folder",
|
||||
expected: []string{"folder"},
|
||||
},
|
||||
{
|
||||
name: "single folder with leading slash",
|
||||
input: "/folder",
|
||||
expected: []string{"folder"},
|
||||
},
|
||||
{
|
||||
name: "nested folder",
|
||||
input: "folder/subfolder/subsubfolder",
|
||||
expected: []string{"folder", "subfolder", "subsubfolder"},
|
||||
},
|
||||
{
|
||||
name: "escaped slashes",
|
||||
input: "folder\\/with\\/slashes",
|
||||
expected: []string{"folder/with/slashes"},
|
||||
},
|
||||
{
|
||||
name: "nested folder with escaped slashes",
|
||||
input: "folder\\/with\\/slashes/subfolder\\/with\\/slashes",
|
||||
expected: []string{"folder/with/slashes", "subfolder/with/slashes"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual := SplitFullpath(tt.input)
|
||||
assert.Equal(t, tt.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/hcl"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
alerting_models "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
@ -28,6 +29,7 @@ type ProvisioningSrv struct {
|
||||
templates TemplateService
|
||||
muteTimings MuteTimingService
|
||||
alertRules AlertRuleService
|
||||
folderSvc folder.Service
|
||||
}
|
||||
|
||||
type ContactPointService interface {
|
||||
@ -66,9 +68,9 @@ type AlertRuleService interface {
|
||||
GetRuleGroup(ctx context.Context, user identity.Requester, folder, group string) (alerting_models.AlertRuleGroup, error)
|
||||
ReplaceRuleGroup(ctx context.Context, user identity.Requester, group alerting_models.AlertRuleGroup, provenance alerting_models.Provenance) error
|
||||
DeleteRuleGroup(ctx context.Context, user identity.Requester, folder, group string, provenance alerting_models.Provenance) error
|
||||
GetAlertRuleWithFolderTitle(ctx context.Context, user identity.Requester, ruleUID string) (provisioning.AlertRuleWithFolderTitle, error)
|
||||
GetAlertRuleGroupWithFolderTitle(ctx context.Context, user identity.Requester, folder, group string) (alerting_models.AlertRuleGroupWithFolderTitle, error)
|
||||
GetAlertGroupsWithFolderTitle(ctx context.Context, user identity.Requester, folderUIDs []string) ([]alerting_models.AlertRuleGroupWithFolderTitle, error)
|
||||
GetAlertRuleWithFolderFullpath(ctx context.Context, u identity.Requester, ruleUID string) (provisioning.AlertRuleWithFolderFullpath, error)
|
||||
GetAlertRuleGroupWithFolderFullpath(ctx context.Context, u identity.Requester, folder, group string) (alerting_models.AlertRuleGroupWithFolderFullpath, error)
|
||||
GetAlertGroupsWithFolderFullpath(ctx context.Context, u identity.Requester, folderUIDs []string) ([]alerting_models.AlertRuleGroupWithFolderFullpath, error)
|
||||
}
|
||||
|
||||
func (srv *ProvisioningSrv) RouteGetPolicyTree(c *contextmodel.ReqContext) response.Response {
|
||||
@ -422,15 +424,15 @@ func (srv *ProvisioningSrv) RouteGetAlertRulesExport(c *contextmodel.ReqContext)
|
||||
return srv.RouteGetAlertRuleGroupExport(c, folderUIDs[0], group)
|
||||
}
|
||||
|
||||
groupsWithTitle, err := srv.alertRules.GetAlertGroupsWithFolderTitle(c.Req.Context(), c.SignedInUser, folderUIDs)
|
||||
groupsWithFullpath, err := srv.alertRules.GetAlertGroupsWithFolderFullpath(c.Req.Context(), c.SignedInUser, folderUIDs)
|
||||
if err != nil {
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "failed to get alert rules", err)
|
||||
}
|
||||
if len(groupsWithTitle) == 0 {
|
||||
if len(groupsWithFullpath) == 0 {
|
||||
return response.Empty(http.StatusNotFound)
|
||||
}
|
||||
|
||||
e, err := AlertingFileExportFromAlertRuleGroupWithFolderTitle(groupsWithTitle)
|
||||
e, err := AlertingFileExportFromAlertRuleGroupWithFolderFullpath(groupsWithFullpath)
|
||||
if err != nil {
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "failed to create alerting file export", err)
|
||||
}
|
||||
@ -440,12 +442,12 @@ func (srv *ProvisioningSrv) RouteGetAlertRulesExport(c *contextmodel.ReqContext)
|
||||
|
||||
// RouteGetAlertRuleGroupExport retrieves the given alert rule group in a format compatible with file provisioning.
|
||||
func (srv *ProvisioningSrv) RouteGetAlertRuleGroupExport(c *contextmodel.ReqContext, folder string, group string) response.Response {
|
||||
g, err := srv.alertRules.GetAlertRuleGroupWithFolderTitle(c.Req.Context(), c.SignedInUser, folder, group)
|
||||
g, err := srv.alertRules.GetAlertRuleGroupWithFolderFullpath(c.Req.Context(), c.SignedInUser, folder, group)
|
||||
if err != nil {
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "failed to get alert rule group", err)
|
||||
}
|
||||
|
||||
e, err := AlertingFileExportFromAlertRuleGroupWithFolderTitle([]alerting_models.AlertRuleGroupWithFolderTitle{g})
|
||||
e, err := AlertingFileExportFromAlertRuleGroupWithFolderFullpath([]alerting_models.AlertRuleGroupWithFolderFullpath{g})
|
||||
if err != nil {
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "failed to create alerting file export", err)
|
||||
}
|
||||
@ -455,7 +457,7 @@ func (srv *ProvisioningSrv) RouteGetAlertRuleGroupExport(c *contextmodel.ReqCont
|
||||
|
||||
// RouteGetAlertRuleExport retrieves the given alert rule in a format compatible with file provisioning.
|
||||
func (srv *ProvisioningSrv) RouteGetAlertRuleExport(c *contextmodel.ReqContext, UID string) response.Response {
|
||||
rule, err := srv.alertRules.GetAlertRuleWithFolderTitle(c.Req.Context(), c.SignedInUser, UID)
|
||||
rule, err := srv.alertRules.GetAlertRuleWithFolderFullpath(c.Req.Context(), c.SignedInUser, UID)
|
||||
if err != nil {
|
||||
if errors.Is(err, alerting_models.ErrAlertRuleNotFound) {
|
||||
return ErrResp(http.StatusNotFound, err, "")
|
||||
@ -463,8 +465,8 @@ func (srv *ProvisioningSrv) RouteGetAlertRuleExport(c *contextmodel.ReqContext,
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "failed to get alert rules", err)
|
||||
}
|
||||
|
||||
e, err := AlertingFileExportFromAlertRuleGroupWithFolderTitle([]alerting_models.AlertRuleGroupWithFolderTitle{
|
||||
alerting_models.NewAlertRuleGroupWithFolderTitleFromRulesGroup(rule.AlertRule.GetGroupKey(), alerting_models.RulesGroup{&rule.AlertRule}, rule.FolderTitle),
|
||||
e, err := AlertingFileExportFromAlertRuleGroupWithFolderFullpath([]alerting_models.AlertRuleGroupWithFolderFullpath{
|
||||
alerting_models.NewAlertRuleGroupWithFolderFullpathFromRulesGroup(rule.AlertRule.GetGroupKey(), alerting_models.RulesGroup{&rule.AlertRule}, rule.FolderFullpath),
|
||||
})
|
||||
if err != nil {
|
||||
return ErrResp(http.StatusInternalServerError, err, "failed to create alerting file export")
|
||||
|
@ -19,23 +19,33 @@ import (
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/log/logtest"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
|
||||
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
secrets_fakes "github.com/grafana/grafana/pkg/services/secrets/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tests/testsuite"
|
||||
@ -317,6 +327,14 @@ func TestProvisioningApi(t *testing.T) {
|
||||
rc.OrgID = 3
|
||||
rule := createTestAlertRule("rule", 1)
|
||||
|
||||
_, err := sut.folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
|
||||
UID: "folder-uid",
|
||||
Title: "Folder Title",
|
||||
OrgID: rc.OrgID,
|
||||
SignedInUser: &user.SignedInUser{OrgID: rc.OrgID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
response := sut.RoutePostAlertRule(&rc, rule)
|
||||
|
||||
require.Equal(t, 201, response.Status())
|
||||
@ -330,7 +348,17 @@ func TestProvisioningApi(t *testing.T) {
|
||||
uid := util.GenerateShortUID()
|
||||
rule := createTestAlertRule("rule", 1)
|
||||
rule.UID = uid
|
||||
insertRuleInOrg(t, sut, rule, 3)
|
||||
|
||||
orgID := int64(3)
|
||||
_, err := sut.folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
|
||||
UID: "folder-uid",
|
||||
Title: "Folder Title",
|
||||
OrgID: orgID,
|
||||
SignedInUser: &user.SignedInUser{OrgID: orgID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
insertRuleInOrg(t, sut, rule, orgID)
|
||||
rc := createTestRequestCtx()
|
||||
rc.Req.Header = map[string][]string{"X-Disable-Provenance": {"hello"}}
|
||||
rc.OrgID = 3
|
||||
@ -1614,6 +1642,7 @@ type testEnvironment struct {
|
||||
quotas provisioning.QuotaChecker
|
||||
prov provisioning.ProvisioningStore
|
||||
ac *recordingAccessControlFake
|
||||
user *user.SignedInUser
|
||||
rulesAuthz *fakes.FakeRuleService
|
||||
}
|
||||
|
||||
@ -1639,20 +1668,8 @@ func createTestEnv(t *testing.T, testConfig string) testEnvironment {
|
||||
GetsConfig(models.AlertConfiguration{
|
||||
AlertmanagerConfiguration: string(raw),
|
||||
})
|
||||
sqlStore := db.InitTestDB(t)
|
||||
sqlStore, cfg := db.InitTestDBWithCfg(t)
|
||||
|
||||
// init folder service with default folder
|
||||
folderService := foldertest.NewFakeService()
|
||||
folderService.ExpectedFolder = &folder.Folder{}
|
||||
|
||||
store := store.DBstore{
|
||||
Logger: log,
|
||||
SQLStore: sqlStore,
|
||||
Cfg: setting.UnifiedAlertingSettings{
|
||||
BaseInterval: time.Second * 10,
|
||||
},
|
||||
FolderService: folderService,
|
||||
}
|
||||
quotas := &provisioning.MockQuotaChecker{}
|
||||
quotas.EXPECT().LimitOK()
|
||||
xact := &provisioning.NopTransactionManager{}
|
||||
@ -1675,6 +1692,49 @@ func createTestEnv(t *testing.T, testConfig string) testEnvironment {
|
||||
}}, nil).Maybe()
|
||||
|
||||
ac := &recordingAccessControlFake{}
|
||||
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
|
||||
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
|
||||
folderService := folderimpl.ProvideService(actest.FakeAccessControl{}, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, sqlStore, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil)
|
||||
store := store.DBstore{
|
||||
Logger: log,
|
||||
SQLStore: sqlStore,
|
||||
Cfg: setting.UnifiedAlertingSettings{
|
||||
BaseInterval: time.Second * 10,
|
||||
},
|
||||
FolderService: folderService,
|
||||
}
|
||||
user := &user.SignedInUser{
|
||||
OrgID: 1,
|
||||
/*
|
||||
Permissions: map[int64]map[string][]string{
|
||||
1: {dashboards.ActionFoldersCreate: {}, dashboards.ActionFoldersRead: {dashboards.ScopeFoldersAll}},
|
||||
},
|
||||
*/
|
||||
}
|
||||
origNewGuardian := guardian.New
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
|
||||
t.Cleanup(func() {
|
||||
guardian.New = origNewGuardian
|
||||
})
|
||||
|
||||
parent, err := folderService.Create(context.Background(), &folder.CreateFolderCommand{
|
||||
OrgID: 1,
|
||||
UID: "folder-uid",
|
||||
Title: "Folder Title",
|
||||
SignedInUser: user,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = folderService.Create(context.Background(), &folder.CreateFolderCommand{
|
||||
OrgID: 1,
|
||||
UID: "folder-uid2",
|
||||
Title: "Folder Title2",
|
||||
ParentUID: parent.UID,
|
||||
SignedInUser: user,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
ruleAuthz := &fakes.FakeRuleService{}
|
||||
|
||||
@ -1689,6 +1749,7 @@ func createTestEnv(t *testing.T, testConfig string) testEnvironment {
|
||||
prov: prov,
|
||||
quotas: quotas,
|
||||
ac: ac,
|
||||
user: user,
|
||||
rulesAuthz: ruleAuthz,
|
||||
}
|
||||
}
|
||||
@ -1710,7 +1771,8 @@ func createProvisioningSrvSutFromEnv(t *testing.T, env *testEnvironment) Provisi
|
||||
contactPointService: provisioning.NewContactPointService(env.configs, env.secrets, env.prov, env.xact, receiverSvc, env.log, env.store),
|
||||
templates: provisioning.NewTemplateService(env.configs, env.prov, env.xact, env.log),
|
||||
muteTimings: provisioning.NewMuteTimingService(env.configs, env.prov, env.xact, env.log),
|
||||
alertRules: provisioning.NewAlertRuleService(env.store, env.prov, env.folderService, env.dashboardService, env.quotas, env.xact, 60, 10, 100, env.log, &provisioning.NotificationSettingsValidatorProviderFake{}, env.rulesAuthz),
|
||||
alertRules: provisioning.NewAlertRuleService(env.store, env.prov, env.folderService, env.quotas, env.xact, 60, 10, 100, env.log, &provisioning.NotificationSettingsValidatorProviderFake{}, env.rulesAuthz),
|
||||
folderSvc: env.folderService,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1725,6 +1787,9 @@ func createTestRequestCtx() contextmodel.ReqContext {
|
||||
},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: 1,
|
||||
Permissions: map[int64]map[string][]string{
|
||||
1: {dashboards.ActionFoldersRead: {dashboards.ScopeFoldersAll}},
|
||||
},
|
||||
},
|
||||
Logger: &logtest.Fake{},
|
||||
}
|
||||
|
@ -35,9 +35,9 @@ func (srv RulerSrv) ExportFromPayload(c *contextmodel.ReqContext, ruleGroupConfi
|
||||
rules = append(rules, optional.AlertRule)
|
||||
}
|
||||
|
||||
groupsWithTitle := ngmodels.NewAlertRuleGroupWithFolderTitle(rules[0].GetGroupKey(), rules, namespace.Title)
|
||||
groupsWithFullpath := ngmodels.NewAlertRuleGroupWithFolderFullpath(rules[0].GetGroupKey(), rules, namespace.Fullpath)
|
||||
|
||||
e, err := AlertingFileExportFromAlertRuleGroupWithFolderTitle([]ngmodels.AlertRuleGroupWithFolderTitle{groupsWithTitle})
|
||||
e, err := AlertingFileExportFromAlertRuleGroupWithFolderFullpath([]ngmodels.AlertRuleGroupWithFolderFullpath{groupsWithFullpath})
|
||||
if err != nil {
|
||||
return ErrResp(http.StatusInternalServerError, err, "failed to create alerting file export")
|
||||
}
|
||||
@ -54,16 +54,16 @@ func (srv RulerSrv) ExportRules(c *contextmodel.ReqContext) response.Response {
|
||||
group := c.Query("group")
|
||||
uid := c.Query("ruleUid")
|
||||
|
||||
var groups []ngmodels.AlertRuleGroupWithFolderTitle
|
||||
var groups []ngmodels.AlertRuleGroupWithFolderFullpath
|
||||
if uid != "" {
|
||||
if group != "" || len(folderUIDs) > 0 {
|
||||
return ErrResp(http.StatusBadRequest, errors.New("group and folder should not be specified when a single rule is requested"), "")
|
||||
}
|
||||
rulesGroup, err := srv.getRuleWithFolderTitleByRuleUid(c, uid)
|
||||
rulesGroup, err := srv.getRuleWithFolderFullpathByRuleUid(c, uid)
|
||||
if err != nil {
|
||||
return errorToResponse(err)
|
||||
}
|
||||
groups = []ngmodels.AlertRuleGroupWithFolderTitle{rulesGroup}
|
||||
groups = []ngmodels.AlertRuleGroupWithFolderFullpath{rulesGroup}
|
||||
} else if group != "" {
|
||||
if len(folderUIDs) != 1 || folderUIDs[0] == "" {
|
||||
return ErrResp(http.StatusBadRequest,
|
||||
@ -71,7 +71,7 @@ func (srv RulerSrv) ExportRules(c *contextmodel.ReqContext) response.Response {
|
||||
"",
|
||||
)
|
||||
}
|
||||
rulesGroup, err := srv.getRuleGroupWithFolderTitle(c, ngmodels.AlertRuleGroupKey{
|
||||
rulesGroup, err := srv.getRuleGroupWithFolderFullPath(c, ngmodels.AlertRuleGroupKey{
|
||||
OrgID: c.SignedInUser.GetOrgID(),
|
||||
NamespaceUID: folderUIDs[0],
|
||||
RuleGroup: group,
|
||||
@ -79,10 +79,10 @@ func (srv RulerSrv) ExportRules(c *contextmodel.ReqContext) response.Response {
|
||||
if err != nil {
|
||||
return errorToResponse(err)
|
||||
}
|
||||
groups = []ngmodels.AlertRuleGroupWithFolderTitle{rulesGroup}
|
||||
groups = []ngmodels.AlertRuleGroupWithFolderFullpath{rulesGroup}
|
||||
} else {
|
||||
var err error
|
||||
groups, err = srv.getRulesWithFolderTitleInFolders(c, folderUIDs)
|
||||
groups, err = srv.getRulesWithFolderFullPathInFolders(c, folderUIDs)
|
||||
if err != nil {
|
||||
return errorToResponse(err)
|
||||
}
|
||||
@ -95,45 +95,45 @@ func (srv RulerSrv) ExportRules(c *contextmodel.ReqContext) response.Response {
|
||||
// sort result so the response is always stable
|
||||
ngmodels.SortAlertRuleGroupWithFolderTitle(groups)
|
||||
|
||||
e, err := AlertingFileExportFromAlertRuleGroupWithFolderTitle(groups)
|
||||
e, err := AlertingFileExportFromAlertRuleGroupWithFolderFullpath(groups)
|
||||
if err != nil {
|
||||
return ErrResp(http.StatusInternalServerError, err, "failed to create alerting file export")
|
||||
}
|
||||
return exportResponse(c, e)
|
||||
}
|
||||
|
||||
// getRuleWithFolderTitleByRuleUid calls getAuthorizedRuleByUid and combines its result with folder (aka namespace) title.
|
||||
func (srv RulerSrv) getRuleWithFolderTitleByRuleUid(c *contextmodel.ReqContext, ruleUID string) (ngmodels.AlertRuleGroupWithFolderTitle, error) {
|
||||
// getRuleWithFolderFullpathByRuleUid calls getAuthorizedRuleByUid and combines its result with folder (aka namespace) title.
|
||||
func (srv RulerSrv) getRuleWithFolderFullpathByRuleUid(c *contextmodel.ReqContext, ruleUID string) (ngmodels.AlertRuleGroupWithFolderFullpath, error) {
|
||||
rule, err := srv.getAuthorizedRuleByUid(c.Req.Context(), c, ruleUID)
|
||||
if err != nil {
|
||||
return ngmodels.AlertRuleGroupWithFolderTitle{}, err
|
||||
return ngmodels.AlertRuleGroupWithFolderFullpath{}, err
|
||||
}
|
||||
namespace, err := srv.store.GetNamespaceByUID(c.Req.Context(), rule.NamespaceUID, c.SignedInUser.GetOrgID(), c.SignedInUser)
|
||||
if err != nil {
|
||||
return ngmodels.AlertRuleGroupWithFolderTitle{}, errors.Join(errFolderAccess, err)
|
||||
return ngmodels.AlertRuleGroupWithFolderFullpath{}, errors.Join(errFolderAccess, err)
|
||||
}
|
||||
return ngmodels.NewAlertRuleGroupWithFolderTitle(rule.GetGroupKey(), []ngmodels.AlertRule{rule}, namespace.Title), nil
|
||||
return ngmodels.NewAlertRuleGroupWithFolderFullpath(rule.GetGroupKey(), []ngmodels.AlertRule{rule}, namespace.Fullpath), nil
|
||||
}
|
||||
|
||||
// getRuleGroupWithFolderTitle calls getAuthorizedRuleGroup and combines its result with folder (aka namespace) title.
|
||||
func (srv RulerSrv) getRuleGroupWithFolderTitle(c *contextmodel.ReqContext, ruleGroupKey ngmodels.AlertRuleGroupKey) (ngmodels.AlertRuleGroupWithFolderTitle, error) {
|
||||
// getRuleGroupWithFolderFullPath calls getAuthorizedRuleGroup and combines its result with folder (aka namespace) title.
|
||||
func (srv RulerSrv) getRuleGroupWithFolderFullPath(c *contextmodel.ReqContext, ruleGroupKey ngmodels.AlertRuleGroupKey) (ngmodels.AlertRuleGroupWithFolderFullpath, error) {
|
||||
namespace, err := srv.store.GetNamespaceByUID(c.Req.Context(), ruleGroupKey.NamespaceUID, c.SignedInUser.GetOrgID(), c.SignedInUser)
|
||||
if err != nil {
|
||||
return ngmodels.AlertRuleGroupWithFolderTitle{}, errors.Join(errFolderAccess, err)
|
||||
return ngmodels.AlertRuleGroupWithFolderFullpath{}, errors.Join(errFolderAccess, err)
|
||||
}
|
||||
rules, err := srv.getAuthorizedRuleGroup(c.Req.Context(), c, ruleGroupKey)
|
||||
if err != nil {
|
||||
return ngmodels.AlertRuleGroupWithFolderTitle{}, err
|
||||
return ngmodels.AlertRuleGroupWithFolderFullpath{}, err
|
||||
}
|
||||
if len(rules) == 0 {
|
||||
return ngmodels.AlertRuleGroupWithFolderTitle{}, ngmodels.ErrAlertRuleNotFound
|
||||
return ngmodels.AlertRuleGroupWithFolderFullpath{}, ngmodels.ErrAlertRuleNotFound
|
||||
}
|
||||
return ngmodels.NewAlertRuleGroupWithFolderTitleFromRulesGroup(ruleGroupKey, rules, namespace.Title), nil
|
||||
return ngmodels.NewAlertRuleGroupWithFolderFullpathFromRulesGroup(ruleGroupKey, rules, namespace.Fullpath), nil
|
||||
}
|
||||
|
||||
// getRulesWithFolderTitleInFolders gets list of folders to which user has access, and then calls searchAuthorizedAlertRules.
|
||||
// getRulesWithFolderFullPathInFolders gets list of folders to which user has access, and then calls searchAuthorizedAlertRules.
|
||||
// If argument folderUIDs is not empty it intersects it with the list of folders available for user and then retrieves rules that are in those folders.
|
||||
func (srv RulerSrv) getRulesWithFolderTitleInFolders(c *contextmodel.ReqContext, folderUIDs []string) ([]ngmodels.AlertRuleGroupWithFolderTitle, error) {
|
||||
func (srv RulerSrv) getRulesWithFolderFullPathInFolders(c *contextmodel.ReqContext, folderUIDs []string) ([]ngmodels.AlertRuleGroupWithFolderFullpath, error) {
|
||||
folders, err := srv.store.GetUserVisibleNamespaces(c.Req.Context(), c.SignedInUser.GetOrgID(), c.SignedInUser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -165,13 +165,13 @@ func (srv RulerSrv) getRulesWithFolderTitleInFolders(c *contextmodel.ReqContext,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]ngmodels.AlertRuleGroupWithFolderTitle, 0, len(rulesByGroup))
|
||||
result := make([]ngmodels.AlertRuleGroupWithFolderFullpath, 0, len(rulesByGroup))
|
||||
for groupKey, rulesGroup := range rulesByGroup {
|
||||
namespace, ok := folders[groupKey.NamespaceUID]
|
||||
if !ok {
|
||||
continue // user does not have access
|
||||
}
|
||||
result = append(result, ngmodels.NewAlertRuleGroupWithFolderTitleFromRulesGroup(groupKey, rulesGroup, namespace.Title))
|
||||
result = append(result, ngmodels.NewAlertRuleGroupWithFolderFullpathFromRulesGroup(groupKey, rulesGroup, namespace.Fullpath))
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
@ -31,8 +31,9 @@ var testData embed.FS
|
||||
func TestExportFromPayload(t *testing.T) {
|
||||
orgID := int64(1)
|
||||
folder := &folder2.Folder{
|
||||
UID: "e4584834-1a87-4dff-8913-8a4748dfca79",
|
||||
Title: "foo bar",
|
||||
UID: "e4584834-1a87-4dff-8913-8a4748dfca79",
|
||||
Title: "foo bar",
|
||||
Fullpath: "foo bar",
|
||||
}
|
||||
|
||||
ruleStore := fakes.NewRuleStore(t)
|
||||
@ -405,12 +406,12 @@ func TestExportRules(t *testing.T) {
|
||||
if tc.expectedStatus != 200 {
|
||||
return
|
||||
}
|
||||
var exp []ngmodels.AlertRuleGroupWithFolderTitle
|
||||
var exp []ngmodels.AlertRuleGroupWithFolderFullpath
|
||||
gr := ngmodels.GroupByAlertRuleGroupKey(tc.expectedRules)
|
||||
for key, rules := range gr {
|
||||
folder, err := ruleStore.GetNamespaceByUID(context.Background(), key.NamespaceUID, orgID, nil)
|
||||
require.NoError(t, err)
|
||||
exp = append(exp, ngmodels.NewAlertRuleGroupWithFolderTitleFromRulesGroup(key, rules, folder.Title))
|
||||
exp = append(exp, ngmodels.NewAlertRuleGroupWithFolderFullpathFromRulesGroup(key, rules, folder.Fullpath))
|
||||
}
|
||||
sort.SliceStable(exp, func(i, j int) bool {
|
||||
gi, gj := exp[i], exp[j]
|
||||
@ -422,7 +423,7 @@ func TestExportRules(t *testing.T) {
|
||||
}
|
||||
return gi.Title < gj.Title
|
||||
})
|
||||
groups, err := AlertingFileExportFromAlertRuleGroupWithFolderTitle(exp)
|
||||
groups, err := AlertingFileExportFromAlertRuleGroupWithFolderFullpath(exp)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, string(exportResponse(rc, groups).Body()), string(resp.Body()))
|
||||
|
@ -135,11 +135,11 @@ func ApiAlertRuleGroupFromAlertRuleGroup(d models.AlertRuleGroup) definitions.Al
|
||||
}
|
||||
}
|
||||
|
||||
// AlertingFileExportFromAlertRuleGroupWithFolderTitle creates an definitions.AlertingFileExport DTO from []models.AlertRuleGroupWithFolderTitle.
|
||||
func AlertingFileExportFromAlertRuleGroupWithFolderTitle(groups []models.AlertRuleGroupWithFolderTitle) (definitions.AlertingFileExport, error) {
|
||||
// AlertingFileExportFromAlertRuleGroupWithFolderFullpath creates an definitions.AlertingFileExport DTO from []models.AlertRuleGroupWithFolderTitle.
|
||||
func AlertingFileExportFromAlertRuleGroupWithFolderFullpath(groups []models.AlertRuleGroupWithFolderFullpath) (definitions.AlertingFileExport, error) {
|
||||
f := definitions.AlertingFileExport{APIVersion: 1}
|
||||
for _, group := range groups {
|
||||
export, err := AlertRuleGroupExportFromAlertRuleGroupWithFolderTitle(group)
|
||||
export, err := AlertRuleGroupExportFromAlertRuleGroupWithFolderFullpath(group)
|
||||
if err != nil {
|
||||
return definitions.AlertingFileExport{}, err
|
||||
}
|
||||
@ -148,8 +148,8 @@ func AlertingFileExportFromAlertRuleGroupWithFolderTitle(groups []models.AlertRu
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// AlertRuleGroupExportFromAlertRuleGroupWithFolderTitle creates a definitions.AlertRuleGroupExport DTO from models.AlertRuleGroup.
|
||||
func AlertRuleGroupExportFromAlertRuleGroupWithFolderTitle(d models.AlertRuleGroupWithFolderTitle) (definitions.AlertRuleGroupExport, error) {
|
||||
// AlertRuleGroupExportFromAlertRuleGroupWithFolderFullpath creates a definitions.AlertRuleGroupExport DTO from models.AlertRuleGroup.
|
||||
func AlertRuleGroupExportFromAlertRuleGroupWithFolderFullpath(d models.AlertRuleGroupWithFolderFullpath) (definitions.AlertRuleGroupExport, error) {
|
||||
rules := make([]definitions.AlertRuleExport, 0, len(d.Rules))
|
||||
for i := range d.Rules {
|
||||
alert, err := AlertRuleExportFromAlertRule(d.Rules[i])
|
||||
@ -161,7 +161,7 @@ func AlertRuleGroupExportFromAlertRuleGroupWithFolderTitle(d models.AlertRuleGro
|
||||
return definitions.AlertRuleGroupExport{
|
||||
OrgID: d.OrgID,
|
||||
Name: d.Title,
|
||||
Folder: d.FolderTitle,
|
||||
Folder: d.FolderFullpath,
|
||||
FolderUID: d.FolderUID,
|
||||
Interval: model.Duration(time.Duration(d.Interval) * time.Second),
|
||||
IntervalSeconds: d.Interval,
|
||||
|
@ -185,42 +185,42 @@ type AlertRuleGroup struct {
|
||||
Rules []AlertRule
|
||||
}
|
||||
|
||||
// AlertRuleGroupWithFolderTitle extends AlertRuleGroup with orgID and folder title
|
||||
type AlertRuleGroupWithFolderTitle struct {
|
||||
// AlertRuleGroupWithFolderFullpath extends AlertRuleGroup with orgID and folder title
|
||||
type AlertRuleGroupWithFolderFullpath struct {
|
||||
*AlertRuleGroup
|
||||
OrgID int64
|
||||
FolderTitle string
|
||||
OrgID int64
|
||||
FolderFullpath string
|
||||
}
|
||||
|
||||
func NewAlertRuleGroupWithFolderTitle(groupKey AlertRuleGroupKey, rules []AlertRule, folderTitle string) AlertRuleGroupWithFolderTitle {
|
||||
func NewAlertRuleGroupWithFolderFullpath(groupKey AlertRuleGroupKey, rules []AlertRule, folderFullpath string) AlertRuleGroupWithFolderFullpath {
|
||||
SortAlertRulesByGroupIndex(rules)
|
||||
var interval int64
|
||||
if len(rules) > 0 {
|
||||
interval = rules[0].IntervalSeconds
|
||||
}
|
||||
var result = AlertRuleGroupWithFolderTitle{
|
||||
var result = AlertRuleGroupWithFolderFullpath{
|
||||
AlertRuleGroup: &AlertRuleGroup{
|
||||
Title: groupKey.RuleGroup,
|
||||
FolderUID: groupKey.NamespaceUID,
|
||||
Interval: interval,
|
||||
Rules: rules,
|
||||
},
|
||||
FolderTitle: folderTitle,
|
||||
OrgID: groupKey.OrgID,
|
||||
FolderFullpath: folderFullpath,
|
||||
OrgID: groupKey.OrgID,
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func NewAlertRuleGroupWithFolderTitleFromRulesGroup(groupKey AlertRuleGroupKey, rules RulesGroup, folderTitle string) AlertRuleGroupWithFolderTitle {
|
||||
func NewAlertRuleGroupWithFolderFullpathFromRulesGroup(groupKey AlertRuleGroupKey, rules RulesGroup, folderFullpath string) AlertRuleGroupWithFolderFullpath {
|
||||
derefRules := make([]AlertRule, 0, len(rules))
|
||||
for _, rule := range rules {
|
||||
derefRules = append(derefRules, *rule)
|
||||
}
|
||||
return NewAlertRuleGroupWithFolderTitle(groupKey, derefRules, folderTitle)
|
||||
return NewAlertRuleGroupWithFolderFullpath(groupKey, derefRules, folderFullpath)
|
||||
}
|
||||
|
||||
// SortAlertRuleGroupWithFolderTitle sorts AlertRuleGroupWithFolderTitle by folder UID and group name
|
||||
func SortAlertRuleGroupWithFolderTitle(g []AlertRuleGroupWithFolderTitle) {
|
||||
func SortAlertRuleGroupWithFolderTitle(g []AlertRuleGroupWithFolderFullpath) {
|
||||
sort.SliceStable(g, func(i, j int) bool {
|
||||
if g[i].AlertRuleGroup.FolderUID == g[j].AlertRuleGroup.FolderUID {
|
||||
return g[i].AlertRuleGroup.Title < g[j].AlertRuleGroup.Title
|
||||
|
@ -354,7 +354,7 @@ func (ng *AlertNG) init() error {
|
||||
contactPointService := provisioning.NewContactPointService(ng.store, ng.SecretsService, ng.store, ng.store, receiverService, ng.Log, ng.store)
|
||||
templateService := provisioning.NewTemplateService(ng.store, ng.store, ng.store, ng.Log)
|
||||
muteTimingService := provisioning.NewMuteTimingService(ng.store, ng.store, ng.store, ng.Log)
|
||||
alertRuleService := provisioning.NewAlertRuleService(ng.store, ng.store, ng.folderService, ng.dashboardService, ng.QuotaService, ng.store,
|
||||
alertRuleService := provisioning.NewAlertRuleService(ng.store, ng.store, ng.folderService, ng.QuotaService, ng.store,
|
||||
int64(ng.Cfg.UnifiedAlerting.DefaultRuleEvaluationInterval.Seconds()),
|
||||
int64(ng.Cfg.UnifiedAlerting.BaseInterval.Seconds()),
|
||||
ng.Cfg.UnifiedAlerting.RulesPerRuleGroupLimit, ng.Log, notifier.NewNotificationSettingsValidationService(ng.store),
|
||||
|
@ -39,7 +39,6 @@ type AlertRuleService struct {
|
||||
ruleStore RuleStore
|
||||
provenanceStore ProvisioningStore
|
||||
folderService folder.Service
|
||||
dashboardService dashboards.DashboardService
|
||||
quotas QuotaChecker
|
||||
xact TransactionManager
|
||||
log log.Logger
|
||||
@ -50,7 +49,6 @@ type AlertRuleService struct {
|
||||
func NewAlertRuleService(ruleStore RuleStore,
|
||||
provenanceStore ProvisioningStore,
|
||||
folderService folder.Service,
|
||||
dashboardService dashboards.DashboardService,
|
||||
quotas QuotaChecker,
|
||||
xact TransactionManager,
|
||||
defaultIntervalSeconds int64,
|
||||
@ -67,7 +65,6 @@ func NewAlertRuleService(ruleStore RuleStore,
|
||||
ruleStore: ruleStore,
|
||||
provenanceStore: provenanceStore,
|
||||
folderService: folderService,
|
||||
dashboardService: dashboardService,
|
||||
quotas: quotas,
|
||||
xact: xact,
|
||||
log: log,
|
||||
@ -147,31 +144,33 @@ func (service *AlertRuleService) GetAlertRule(ctx context.Context, user identity
|
||||
return rule, provenance, nil
|
||||
}
|
||||
|
||||
type AlertRuleWithFolderTitle struct {
|
||||
AlertRule models.AlertRule
|
||||
FolderTitle string
|
||||
type AlertRuleWithFolderFullpath struct {
|
||||
AlertRule models.AlertRule
|
||||
FolderFullpath string
|
||||
}
|
||||
|
||||
// GetAlertRuleWithFolderTitle returns a single alert rule with its folder title.
|
||||
func (service *AlertRuleService) GetAlertRuleWithFolderTitle(ctx context.Context, user identity.Requester, ruleUID string) (AlertRuleWithFolderTitle, error) {
|
||||
// GetAlertRuleWithFolderFullpath returns a single alert rule with its folder title.
|
||||
func (service *AlertRuleService) GetAlertRuleWithFolderFullpath(ctx context.Context, user identity.Requester, ruleUID string) (AlertRuleWithFolderFullpath, error) {
|
||||
rule, err := service.getAlertRuleAuthorized(ctx, user, ruleUID)
|
||||
if err != nil {
|
||||
return AlertRuleWithFolderTitle{}, err
|
||||
return AlertRuleWithFolderFullpath{}, err
|
||||
}
|
||||
|
||||
dq := dashboards.GetDashboardQuery{
|
||||
OrgID: user.GetOrgID(),
|
||||
UID: rule.NamespaceUID,
|
||||
fq := folder.GetFolderQuery{
|
||||
OrgID: user.GetOrgID(),
|
||||
UID: &rule.NamespaceUID,
|
||||
WithFullpath: true,
|
||||
SignedInUser: user,
|
||||
}
|
||||
|
||||
dash, err := service.dashboardService.GetDashboard(ctx, &dq)
|
||||
f, err := service.folderService.Get(ctx, &fq)
|
||||
if err != nil {
|
||||
return AlertRuleWithFolderTitle{}, err
|
||||
return AlertRuleWithFolderFullpath{}, err
|
||||
}
|
||||
|
||||
return AlertRuleWithFolderTitle{
|
||||
AlertRule: rule,
|
||||
FolderTitle: dash.Title,
|
||||
return AlertRuleWithFolderFullpath{
|
||||
AlertRule: rule,
|
||||
FolderFullpath: f.Fullpath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -699,28 +698,30 @@ func (service *AlertRuleService) deleteRules(ctx context.Context, orgID int64, t
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAlertRuleGroupWithFolderTitle returns the alert rule group with folder title.
|
||||
func (service *AlertRuleService) GetAlertRuleGroupWithFolderTitle(ctx context.Context, user identity.Requester, namespaceUID, group string) (models.AlertRuleGroupWithFolderTitle, error) {
|
||||
// GetAlertRuleGroupWithFolderFullpath returns the alert rule group with folder title.
|
||||
func (service *AlertRuleService) GetAlertRuleGroupWithFolderFullpath(ctx context.Context, user identity.Requester, namespaceUID, group string) (models.AlertRuleGroupWithFolderFullpath, error) {
|
||||
ruleList, err := service.GetRuleGroup(ctx, user, namespaceUID, group)
|
||||
if err != nil {
|
||||
return models.AlertRuleGroupWithFolderTitle{}, err
|
||||
return models.AlertRuleGroupWithFolderFullpath{}, err
|
||||
}
|
||||
|
||||
dq := dashboards.GetDashboardQuery{
|
||||
OrgID: user.GetOrgID(),
|
||||
UID: namespaceUID,
|
||||
fq := folder.GetFolderQuery{
|
||||
OrgID: user.GetOrgID(),
|
||||
UID: &namespaceUID,
|
||||
WithFullpath: true,
|
||||
SignedInUser: user,
|
||||
}
|
||||
dash, err := service.dashboardService.GetDashboard(ctx, &dq)
|
||||
f, err := service.folderService.Get(ctx, &fq)
|
||||
if err != nil {
|
||||
return models.AlertRuleGroupWithFolderTitle{}, err
|
||||
return models.AlertRuleGroupWithFolderFullpath{}, err
|
||||
}
|
||||
|
||||
res := models.NewAlertRuleGroupWithFolderTitle(ruleList.Rules[0].GetGroupKey(), ruleList.Rules, dash.Title)
|
||||
res := models.NewAlertRuleGroupWithFolderFullpath(ruleList.Rules[0].GetGroupKey(), ruleList.Rules, f.Fullpath)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetAlertGroupsWithFolderTitle returns all groups with folder title in the folders identified by folderUID that have at least one alert. If argument folderUIDs is nil or empty - returns groups in all folders.
|
||||
func (service *AlertRuleService) GetAlertGroupsWithFolderTitle(ctx context.Context, user identity.Requester, folderUIDs []string) ([]models.AlertRuleGroupWithFolderTitle, error) {
|
||||
// GetAlertGroupsWithFolderFullpath returns all groups with folder's full path in the folders identified by folderUID that have at least one alert. If argument folderUIDs is nil or empty - returns groups in all folders.
|
||||
func (service *AlertRuleService) GetAlertGroupsWithFolderFullpath(ctx context.Context, user identity.Requester, folderUIDs []string) ([]models.AlertRuleGroupWithFolderFullpath, error) {
|
||||
q := models.ListAlertRulesQuery{
|
||||
OrgID: user.GetOrgID(),
|
||||
}
|
||||
@ -758,33 +759,36 @@ func (service *AlertRuleService) GetAlertGroupsWithFolderTitle(ctx context.Conte
|
||||
}
|
||||
|
||||
if len(namespaces) == 0 {
|
||||
return []models.AlertRuleGroupWithFolderTitle{}, nil
|
||||
return []models.AlertRuleGroupWithFolderFullpath{}, nil
|
||||
}
|
||||
|
||||
dq := dashboards.GetDashboardsQuery{
|
||||
DashboardUIDs: nil,
|
||||
fq := folder.GetFoldersQuery{
|
||||
OrgID: user.GetOrgID(),
|
||||
UIDs: nil,
|
||||
WithFullpath: true,
|
||||
SignedInUser: user,
|
||||
}
|
||||
for uid := range namespaces {
|
||||
dq.DashboardUIDs = append(dq.DashboardUIDs, uid)
|
||||
fq.UIDs = append(fq.UIDs, uid)
|
||||
}
|
||||
|
||||
// We need folder titles for the provisioning file format. We do it this way instead of using GetUserVisibleNamespaces to avoid folder:read permissions that should not apply to those with alert.provisioning:read.
|
||||
dashes, err := service.dashboardService.GetDashboards(ctx, &dq)
|
||||
folders, err := service.folderService.GetFolders(ctx, fq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
folderUidToTitle := make(map[string]string)
|
||||
for _, dash := range dashes {
|
||||
folderUidToTitle[dash.UID] = dash.Title
|
||||
folderUidToFullpath := make(map[string]string)
|
||||
for _, folder := range folders {
|
||||
folderUidToFullpath[folder.UID] = folder.Fullpath
|
||||
}
|
||||
|
||||
result := make([]models.AlertRuleGroupWithFolderTitle, 0)
|
||||
result := make([]models.AlertRuleGroupWithFolderFullpath, 0)
|
||||
for groupKey, rules := range groups {
|
||||
title, ok := folderUidToTitle[groupKey.NamespaceUID]
|
||||
fullpath, ok := folderUidToFullpath[groupKey.NamespaceUID]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot find title for folder with uid '%s'", groupKey.NamespaceUID)
|
||||
return nil, fmt.Errorf("cannot find full path for folder with uid '%s'", groupKey.NamespaceUID)
|
||||
}
|
||||
result = append(result, models.NewAlertRuleGroupWithFolderTitleFromRulesGroup(groupKey, rules, title))
|
||||
result = append(result, models.NewAlertRuleGroupWithFolderFullpathFromRulesGroup(groupKey, rules, fullpath))
|
||||
}
|
||||
|
||||
// Return results in a stable manner.
|
||||
|
@ -13,24 +13,32 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/expr"
|
||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
|
||||
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/testutil"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func TestAlertRuleService(t *testing.T) {
|
||||
ruleService := createAlertRuleService(t)
|
||||
ruleService := createAlertRuleService(t, nil)
|
||||
var orgID int64 = 1
|
||||
u := &user.SignedInUser{
|
||||
UserID: 1,
|
||||
@ -82,7 +90,7 @@ func TestAlertRuleService(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("group update should propagate folderUID from group to rules", func(t *testing.T) {
|
||||
ruleService := createAlertRuleService(t)
|
||||
ruleService := createAlertRuleService(t, nil)
|
||||
group := createDummyGroup("namespace-test", orgID)
|
||||
group.Rules[0].NamespaceUID = ""
|
||||
|
||||
@ -523,7 +531,7 @@ func TestAlertRuleService(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("quota met causes create to be rejected", func(t *testing.T) {
|
||||
ruleService := createAlertRuleService(t)
|
||||
ruleService := createAlertRuleService(t, nil)
|
||||
checker := &MockQuotaChecker{}
|
||||
checker.EXPECT().LimitExceeded()
|
||||
ruleService.quotas = checker
|
||||
@ -534,7 +542,7 @@ func TestAlertRuleService(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("quota met causes group write to be rejected", func(t *testing.T) {
|
||||
ruleService := createAlertRuleService(t)
|
||||
ruleService := createAlertRuleService(t, nil)
|
||||
checker := &MockQuotaChecker{}
|
||||
checker.EXPECT().LimitExceeded()
|
||||
ruleService.quotas = checker
|
||||
@ -757,7 +765,7 @@ func TestCreateAlertRule(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
ruleService := createAlertRuleService(t)
|
||||
ruleService := createAlertRuleService(t, nil)
|
||||
t.Run("should return the created id", func(t *testing.T) {
|
||||
rule, err := ruleService.CreateAlertRule(context.Background(), u, dummyRule("test#1", orgID), models.ProvenanceNone)
|
||||
require.NoError(t, err)
|
||||
@ -1463,6 +1471,88 @@ func TestDeleteRuleGroup(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestProvisiongWithFullpath(t *testing.T) {
|
||||
tracer := tracing.InitializeTracerForTest()
|
||||
inProcBus := bus.ProvideBus(tracer)
|
||||
sqlStore := db.InitTestDB(t)
|
||||
cfg := setting.NewCfg()
|
||||
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
|
||||
_, dashboardStore := testutil.SetupDashboardService(t, sqlStore, folderStore, cfg)
|
||||
ac := acmock.New()
|
||||
features := featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)
|
||||
folderService := folderimpl.ProvideService(ac, inProcBus, dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil)
|
||||
|
||||
ruleService := createAlertRuleService(t, folderService)
|
||||
var orgID int64 = 1
|
||||
|
||||
signedInUser := user.SignedInUser{UserID: 1, OrgID: orgID, Permissions: map[int64]map[string][]string{
|
||||
orgID: {
|
||||
dashboards.ActionFoldersCreate: {},
|
||||
dashboards.ActionFoldersRead: {dashboards.ScopeFoldersAll},
|
||||
dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll}},
|
||||
}}
|
||||
namespaceUID := "my-namespace"
|
||||
namespaceTitle := namespaceUID
|
||||
rootFolder, err := folderService.Create(context.Background(), &folder.CreateFolderCommand{
|
||||
UID: namespaceUID,
|
||||
Title: namespaceTitle,
|
||||
OrgID: orgID,
|
||||
SignedInUser: &signedInUser,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("for a rule under a root folder should set the right fullpath", func(t *testing.T) {
|
||||
r, err := ruleService.ruleStore.InsertAlertRules(context.Background(), []models.AlertRule{
|
||||
createTestRule("my-cool-group", "my-cool-group", orgID, namespaceUID),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, r, 1)
|
||||
|
||||
res, err := ruleService.GetAlertRuleWithFolderFullpath(context.Background(), &signedInUser, r[0].UID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, namespaceTitle, res.FolderFullpath)
|
||||
|
||||
res2, err := ruleService.GetAlertRuleGroupWithFolderFullpath(context.Background(), &signedInUser, namespaceUID, "my-cool-group")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, namespaceTitle, res2.FolderFullpath)
|
||||
|
||||
res3, err := ruleService.GetAlertGroupsWithFolderFullpath(context.Background(), &signedInUser, []string{namespaceUID})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, namespaceTitle, res3[0].FolderFullpath)
|
||||
})
|
||||
|
||||
t.Run("for a rule under a subfolder should set the right fullpath", func(t *testing.T) {
|
||||
otherNamespaceUID := "my-other-namespace"
|
||||
otherNamespaceTitle := "my-other-namespace containing multiple //"
|
||||
_, err := folderService.Create(context.Background(), &folder.CreateFolderCommand{
|
||||
UID: otherNamespaceUID,
|
||||
Title: otherNamespaceTitle,
|
||||
OrgID: orgID,
|
||||
ParentUID: rootFolder.UID,
|
||||
SignedInUser: &signedInUser,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
r, err := ruleService.ruleStore.InsertAlertRules(context.Background(), []models.AlertRule{
|
||||
createTestRule("my-cool-group-2", "my-cool-group-2", orgID, otherNamespaceUID),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, r, 1)
|
||||
|
||||
res, err := ruleService.GetAlertRuleWithFolderFullpath(context.Background(), &signedInUser, r[0].UID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "my-namespace/my-other-namespace containing multiple \\/\\/", res.FolderFullpath)
|
||||
|
||||
res2, err := ruleService.GetAlertRuleGroupWithFolderFullpath(context.Background(), &signedInUser, otherNamespaceUID, "my-cool-group-2")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "my-namespace/my-other-namespace containing multiple \\/\\/", res2.FolderFullpath)
|
||||
|
||||
res3, err := ruleService.GetAlertGroupsWithFolderFullpath(context.Background(), &signedInUser, []string{otherNamespaceUID})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "my-namespace/my-other-namespace containing multiple \\/\\/", res3[0].FolderFullpath)
|
||||
})
|
||||
}
|
||||
|
||||
func getDeleteQueries(ruleStore *fakes.RuleStore) []fakes.GenericRecordedQuery {
|
||||
generic := ruleStore.GetRecordedCommands(func(cmd any) (any, bool) {
|
||||
a, ok := cmd.(fakes.GenericRecordedQuery)
|
||||
@ -1478,13 +1568,9 @@ func getDeleteQueries(ruleStore *fakes.RuleStore) []fakes.GenericRecordedQuery {
|
||||
return result
|
||||
}
|
||||
|
||||
func createAlertRuleService(t *testing.T) AlertRuleService {
|
||||
func createAlertRuleService(t *testing.T, folderService folder.Service) AlertRuleService {
|
||||
t.Helper()
|
||||
sqlStore := db.InitTestDB(t)
|
||||
folderService := foldertest.NewFakeService()
|
||||
folderService.ExpectedFolder = &folder.Folder{
|
||||
UID: "default-namespace",
|
||||
}
|
||||
store := store.DBstore{
|
||||
SQLStore: sqlStore,
|
||||
Cfg: setting.UnifiedAlertingSettings{
|
||||
@ -1496,6 +1582,11 @@ func createAlertRuleService(t *testing.T) AlertRuleService {
|
||||
// store := fakes.NewRuleStore(t)
|
||||
quotas := MockQuotaChecker{}
|
||||
quotas.EXPECT().LimitOK()
|
||||
|
||||
if folderService == nil {
|
||||
folderService = foldertest.NewFakeService()
|
||||
}
|
||||
|
||||
return AlertRuleService{
|
||||
ruleStore: store,
|
||||
provenanceStore: store,
|
||||
|
@ -6,12 +6,13 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
||||
)
|
||||
|
||||
type ProvisionerConfig struct {
|
||||
Path string
|
||||
DashboardService dashboards.DashboardService
|
||||
FolderService folder.Service
|
||||
DashboardProvService dashboards.DashboardProvisioningService
|
||||
RuleService provisioning.AlertRuleService
|
||||
ContactPointService provisioning.ContactPointService
|
||||
@ -63,7 +64,7 @@ func Provision(ctx context.Context, cfg ProvisionerConfig) error {
|
||||
}
|
||||
ruleProvisioner := NewAlertRuleProvisioner(
|
||||
logger,
|
||||
cfg.DashboardService,
|
||||
cfg.FolderService,
|
||||
cfg.DashboardProvService,
|
||||
cfg.RuleService)
|
||||
err = ruleProvisioner.Provision(ctx, files)
|
||||
|
@ -6,11 +6,11 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
|
||||
alert_models "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
@ -23,12 +23,12 @@ type AlertRuleProvisioner interface {
|
||||
|
||||
func NewAlertRuleProvisioner(
|
||||
logger log.Logger,
|
||||
dashboardService dashboards.DashboardService,
|
||||
folderService folder.Service,
|
||||
dashboardProvService dashboards.DashboardProvisioningService,
|
||||
ruleService provisioning.AlertRuleService) AlertRuleProvisioner {
|
||||
return &defaultAlertRuleProvisioner{
|
||||
logger: logger,
|
||||
dashboardService: dashboardService,
|
||||
folderService: folderService,
|
||||
dashboardProvService: dashboardProvService,
|
||||
ruleService: ruleService,
|
||||
}
|
||||
@ -36,7 +36,7 @@ func NewAlertRuleProvisioner(
|
||||
|
||||
type defaultAlertRuleProvisioner struct {
|
||||
logger log.Logger
|
||||
dashboardService dashboards.DashboardService
|
||||
folderService folder.Service
|
||||
dashboardProvService dashboards.DashboardProvisioningService
|
||||
ruleService provisioning.AlertRuleService
|
||||
}
|
||||
@ -46,13 +46,14 @@ func (prov *defaultAlertRuleProvisioner) Provision(ctx context.Context,
|
||||
for _, file := range files {
|
||||
for _, group := range file.Groups {
|
||||
u := provisionerUser(group.OrgID)
|
||||
folderUID, err := prov.getOrCreateFolderUID(ctx, group.FolderTitle, group.OrgID)
|
||||
folderUID, err := prov.getOrCreateFolderFullpath(ctx, group.FolderFullpath, group.OrgID)
|
||||
if err != nil {
|
||||
prov.logger.Error("failed to get or create folder", "folder", group.FolderFullpath, "org", group.OrgID, "err", err)
|
||||
return err
|
||||
}
|
||||
prov.logger.Debug("provisioning alert rule group",
|
||||
"org", group.OrgID,
|
||||
"folder", group.FolderTitle,
|
||||
"folder", group.FolderFullpath,
|
||||
"folderUID", folderUID,
|
||||
"name", group.Title)
|
||||
for _, rule := range group.Rules {
|
||||
@ -98,36 +99,57 @@ func (prov *defaultAlertRuleProvisioner) provisionRule(
|
||||
return err
|
||||
}
|
||||
|
||||
func (prov *defaultAlertRuleProvisioner) getOrCreateFolderUID(
|
||||
ctx context.Context, folderName string, orgID int64) (string, error) {
|
||||
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Provisioning).Inc()
|
||||
cmd := &dashboards.GetDashboardQuery{
|
||||
Title: &folderName,
|
||||
FolderID: util.Pointer(int64(0)), // nolint:staticcheck
|
||||
OrgID: orgID,
|
||||
func (prov *defaultAlertRuleProvisioner) getOrCreateFolderFullpath(
|
||||
ctx context.Context, folderFullpath string, orgID int64) (string, error) {
|
||||
folderTitles := folderimpl.SplitFullpath(folderFullpath)
|
||||
if len(folderTitles) == 0 {
|
||||
return "", fmt.Errorf("invalid folder fullpath: %s", folderFullpath)
|
||||
}
|
||||
cmdResult, err := prov.dashboardService.GetDashboard(ctx, cmd)
|
||||
if err != nil && !errors.Is(err, dashboards.ErrDashboardNotFound) {
|
||||
|
||||
var folderUID *string
|
||||
for i := range folderTitles {
|
||||
uid, err := prov.getOrCreateFolderByTitle(ctx, folderTitles[i], orgID, folderUID)
|
||||
if err != nil {
|
||||
prov.logger.Error("failed to get or create folder", "folder", folderTitles[i], "org", orgID, "err", err)
|
||||
return "", err
|
||||
}
|
||||
folderUID = &uid
|
||||
}
|
||||
return *folderUID, nil
|
||||
}
|
||||
|
||||
func (prov *defaultAlertRuleProvisioner) getOrCreateFolderByTitle(
|
||||
ctx context.Context, folderName string, orgID int64, parentUID *string) (string, error) {
|
||||
cmd := &folder.GetFolderQuery{
|
||||
Title: &folderName,
|
||||
ParentUID: parentUID,
|
||||
OrgID: orgID,
|
||||
SignedInUser: provisionerUser(orgID),
|
||||
}
|
||||
|
||||
cmdResult, err := prov.folderService.Get(ctx, cmd)
|
||||
if err != nil && !errors.Is(err, dashboards.ErrFolderNotFound) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// dashboard folder not found. create one.
|
||||
if errors.Is(err, dashboards.ErrDashboardNotFound) {
|
||||
if errors.Is(err, dashboards.ErrFolderNotFound) {
|
||||
createCmd := &folder.CreateFolderCommand{
|
||||
OrgID: orgID,
|
||||
UID: util.GenerateShortUID(),
|
||||
Title: folderName,
|
||||
}
|
||||
dbDash, err := prov.dashboardProvService.SaveFolderForProvisionedDashboards(ctx, createCmd)
|
||||
|
||||
if parentUID != nil {
|
||||
createCmd.ParentUID = *parentUID
|
||||
}
|
||||
|
||||
f, err := prov.dashboardProvService.SaveFolderForProvisionedDashboards(ctx, createCmd)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return dbDash.UID, nil
|
||||
}
|
||||
|
||||
if !cmdResult.IsFolder {
|
||||
return "", fmt.Errorf("got invalid response. expected folder, found dashboard")
|
||||
return f.UID, nil
|
||||
}
|
||||
|
||||
return cmdResult.UID, nil
|
||||
|
@ -32,11 +32,11 @@ type AlertRuleGroupV1 struct {
|
||||
Rules []AlertRuleV1 `json:"rules" yaml:"rules"`
|
||||
}
|
||||
|
||||
func (ruleGroupV1 *AlertRuleGroupV1) MapToModel() (models.AlertRuleGroupWithFolderTitle, error) {
|
||||
ruleGroup := models.AlertRuleGroupWithFolderTitle{AlertRuleGroup: &models.AlertRuleGroup{}}
|
||||
func (ruleGroupV1 *AlertRuleGroupV1) MapToModel() (models.AlertRuleGroupWithFolderFullpath, error) {
|
||||
ruleGroup := models.AlertRuleGroupWithFolderFullpath{AlertRuleGroup: &models.AlertRuleGroup{}}
|
||||
ruleGroup.Title = ruleGroupV1.Name.Value()
|
||||
if strings.TrimSpace(ruleGroup.Title) == "" {
|
||||
return models.AlertRuleGroupWithFolderTitle{}, errors.New("rule group has no name set")
|
||||
return models.AlertRuleGroupWithFolderFullpath{}, errors.New("rule group has no name set")
|
||||
}
|
||||
ruleGroup.OrgID = ruleGroupV1.OrgID.Value()
|
||||
if ruleGroup.OrgID < 1 {
|
||||
@ -44,17 +44,17 @@ func (ruleGroupV1 *AlertRuleGroupV1) MapToModel() (models.AlertRuleGroupWithFold
|
||||
}
|
||||
interval, err := model.ParseDuration(ruleGroupV1.Interval.Value())
|
||||
if err != nil {
|
||||
return models.AlertRuleGroupWithFolderTitle{}, err
|
||||
return models.AlertRuleGroupWithFolderFullpath{}, err
|
||||
}
|
||||
ruleGroup.Interval = int64(time.Duration(interval).Seconds())
|
||||
ruleGroup.FolderTitle = ruleGroupV1.Folder.Value()
|
||||
if strings.TrimSpace(ruleGroup.FolderTitle) == "" {
|
||||
return models.AlertRuleGroupWithFolderTitle{}, errors.New("rule group has no folder set")
|
||||
ruleGroup.FolderFullpath = ruleGroupV1.Folder.Value()
|
||||
if strings.TrimSpace(ruleGroup.FolderFullpath) == "" {
|
||||
return models.AlertRuleGroupWithFolderFullpath{}, errors.New("rule group has no folder set")
|
||||
}
|
||||
for _, ruleV1 := range ruleGroupV1.Rules {
|
||||
rule, err := ruleV1.mapToModel(ruleGroup.OrgID)
|
||||
if err != nil {
|
||||
return models.AlertRuleGroupWithFolderTitle{}, err
|
||||
return models.AlertRuleGroupWithFolderFullpath{}, err
|
||||
}
|
||||
ruleGroup.Rules = append(ruleGroup.Rules, rule)
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ type OrgID int64
|
||||
type AlertingFile struct {
|
||||
configVersion
|
||||
Filename string
|
||||
Groups []models.AlertRuleGroupWithFolderTitle
|
||||
Groups []models.AlertRuleGroupWithFolderFullpath
|
||||
DeleteRules []RuleDelete
|
||||
ContactPoints []ContactPoint
|
||||
DeleteContactPoints []DeleteContactPoint
|
||||
|
@ -337,6 +337,7 @@ func (fr *FileReader) getOrCreateFolder(ctx context.Context, cfg *config, servic
|
||||
return 0, "", ErrFolderNameMissing
|
||||
}
|
||||
|
||||
// TODO use folder service instead
|
||||
metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Provisioning).Inc()
|
||||
cmd := &dashboards.GetDashboardQuery{
|
||||
FolderID: util.Pointer(int64(0)), // nolint:staticcheck
|
||||
|
@ -114,7 +114,7 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
cfg.Folder = "Team A"
|
||||
|
||||
fakeService.On("GetProvisionedDashboardData", mock.Anything, configName).Return(nil, nil).Once()
|
||||
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&folder.Folder{}, nil).Once()
|
||||
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&folder.Folder{ID: 1}, nil).Once()
|
||||
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{ID: 2}, nil).Times(2)
|
||||
|
||||
reader, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil)
|
||||
@ -380,7 +380,7 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
"folder": defaultDashboards,
|
||||
},
|
||||
}
|
||||
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&folder.Folder{}, nil).Once()
|
||||
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&folder.Folder{ID: 1}, nil).Once()
|
||||
|
||||
r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore, nil)
|
||||
require.NoError(t, err)
|
||||
|
@ -259,8 +259,8 @@ func (ps *ProvisioningServiceImpl) ProvisionAlerting(ctx context.Context) error
|
||||
ruleService := provisioning.NewAlertRuleService(
|
||||
st,
|
||||
st,
|
||||
nil,
|
||||
ps.dashboardService,
|
||||
ps.folderService,
|
||||
//ps.dashboardService,
|
||||
ps.quotaService,
|
||||
ps.SQLStore,
|
||||
int64(ps.Cfg.UnifiedAlerting.DefaultRuleEvaluationInterval.Seconds()),
|
||||
@ -280,7 +280,7 @@ func (ps *ProvisioningServiceImpl) ProvisionAlerting(ctx context.Context) error
|
||||
cfg := prov_alerting.ProvisionerConfig{
|
||||
Path: alertingPath,
|
||||
RuleService: *ruleService,
|
||||
DashboardService: ps.dashboardService,
|
||||
FolderService: ps.folderService,
|
||||
DashboardProvService: ps.dashboardProvisioningService,
|
||||
ContactPointService: *contactPointService,
|
||||
NotificiationPolicyService: *notificationPolicyService,
|
||||
|
@ -509,7 +509,7 @@ func TestIntegrationAlertRuleNestedPermissions(t *testing.T) {
|
||||
|
||||
require.Equal(t, "folder1", allExport.Groups[0].Folder)
|
||||
require.Equal(t, "folder2", allExport.Groups[1].Folder)
|
||||
require.Equal(t, "subfolder", allExport.Groups[2].Folder)
|
||||
require.Equal(t, "folder1/subfolder", allExport.Groups[2].Folder)
|
||||
})
|
||||
|
||||
t.Run("Export from one folder", func(t *testing.T) {
|
||||
@ -632,7 +632,7 @@ func TestIntegrationAlertRuleNestedPermissions(t *testing.T) {
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
require.Len(t, export.Groups, 2)
|
||||
require.Equal(t, "folder1", export.Groups[0].Folder)
|
||||
require.Equal(t, "subfolder", export.Groups[1].Folder)
|
||||
require.Equal(t, "folder1/subfolder", export.Groups[1].Folder)
|
||||
})
|
||||
|
||||
t.Run("Export from one folder", func(t *testing.T) {
|
||||
|
Loading…
Reference in New Issue
Block a user