mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Update API to use folders' full paths (#81214)
* update GetUserVisibleNamespaces to use FolderSeriver * update GetNamespaceByUID to use FolderService.GetFolders * update GetAlertRulesForScheduling to use FolderService.GetFolders * Update API and GetAlertRulesForScheduling to use the folder's full path * get full path of folder in RouteTestGrafanaRuleConfig * fix escaping of titles for MySQL
This commit is contained in:
parent
6f8852095e
commit
47546a4c72
@ -530,10 +530,14 @@ func (ss *sqlStore) GetDescendants(ctx context.Context, orgID int64, ancestor_ui
|
||||
}
|
||||
|
||||
func getFullpathSQL(dialect migrator.Dialect) string {
|
||||
escaped := "\\/"
|
||||
if dialect.DriverName() == migrator.MySQL {
|
||||
escaped = "\\\\/"
|
||||
}
|
||||
concatCols := make([]string, 0, folder.MaxNestedFolderDepth)
|
||||
concatCols = append(concatCols, "COALESCE(REPLACE(f0.title, '/', '\\/'), '')")
|
||||
concatCols = append(concatCols, fmt.Sprintf("COALESCE(REPLACE(f0.title, '/', '%s'), '')", escaped))
|
||||
for i := 1; i <= folder.MaxNestedFolderDepth; i++ {
|
||||
concatCols = append([]string{fmt.Sprintf("COALESCE(REPLACE(f%d.title, '/', '\\/'), '')", i), "'/'"}, concatCols...)
|
||||
concatCols = append([]string{fmt.Sprintf("COALESCE(REPLACE(f%d.title, '/', '%s'), '')", i, escaped), "'/'"}, concatCols...)
|
||||
}
|
||||
return dialect.Concat(concatCols...)
|
||||
}
|
||||
|
@ -129,6 +129,7 @@ func (api *API) RegisterAPIEndpoints(m *metrics.API) {
|
||||
featureManager: api.FeatureManager,
|
||||
appUrl: api.AppUrl,
|
||||
tracer: api.Tracer,
|
||||
folderService: api.RuleStore,
|
||||
}), m)
|
||||
api.RegisterConfigurationApiEndpoints(NewConfiguration(
|
||||
&ConfigSrv{
|
||||
|
@ -304,7 +304,7 @@ func (srv PrometheusSrv) toRuleGroup(groupKey ngmodels.AlertRuleGroupKey, folder
|
||||
newGroup := &apimodels.RuleGroup{
|
||||
Name: groupKey.RuleGroup,
|
||||
// file is what Prometheus uses for provisioning, we replace it with namespace which is the folder in Grafana.
|
||||
File: ngmodels.GetNamespaceKey(folder.ParentUID, folder.Title),
|
||||
File: folder.Fullpath,
|
||||
}
|
||||
|
||||
rulesTotals := make(map[string]int64, len(rules))
|
||||
|
@ -162,9 +162,7 @@ func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *contextmodel.ReqContext, nam
|
||||
result := apimodels.NamespaceConfigResponse{}
|
||||
|
||||
for groupKey, rules := range ruleGroups {
|
||||
key := ngmodels.GetNamespaceKey(namespace.ParentUID, namespace.Title)
|
||||
// nolint:staticcheck
|
||||
result[key] = append(result[key], toGettableRuleGroupConfig(groupKey.RuleGroup, rules, provenanceRecords))
|
||||
result[namespace.Fullpath] = append(result[namespace.Fullpath], toGettableRuleGroupConfig(groupKey.RuleGroup, rules, provenanceRecords))
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusAccepted, result)
|
||||
@ -242,9 +240,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)
|
||||
continue
|
||||
}
|
||||
key := ngmodels.GetNamespaceKey(folder.ParentUID, folder.Title)
|
||||
// nolint:staticcheck
|
||||
result[key] = append(result[key], toGettableRuleGroupConfig(groupKey.RuleGroup, rules, provenanceRecords))
|
||||
result[folder.Fullpath] = append(result[folder.Fullpath], toGettableRuleGroupConfig(groupKey.RuleGroup, rules, provenanceRecords))
|
||||
}
|
||||
return response.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
@ -200,7 +200,7 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) {
|
||||
require.NoError(t, json.Unmarshal(response.Body(), result))
|
||||
require.NotNil(t, result)
|
||||
for namespace, groups := range *result {
|
||||
require.Equal(t, models.GetNamespaceKey(folder.ParentUID, folder.Title), namespace)
|
||||
require.Equal(t, folder.Fullpath, namespace)
|
||||
for _, group := range groups {
|
||||
grouploop:
|
||||
for _, actualRule := range group.Rules {
|
||||
@ -243,7 +243,7 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) {
|
||||
require.NotNil(t, result)
|
||||
found := false
|
||||
for namespace, groups := range *result {
|
||||
require.Equal(t, models.GetNamespaceKey(folder.ParentUID, folder.Title), namespace)
|
||||
require.Equal(t, folder.Fullpath, namespace)
|
||||
for _, group := range groups {
|
||||
for _, actualRule := range group.Rules {
|
||||
if actualRule.GrafanaManagedAlert.UID == expectedRules[0].UID {
|
||||
@ -278,7 +278,7 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) {
|
||||
|
||||
models.RulesGroup(expectedRules).SortByGroupIndex()
|
||||
|
||||
groups, ok := (*result)[models.GetNamespaceKey(folder.ParentUID, folder.Title)]
|
||||
groups, ok := (*result)[folder.Fullpath]
|
||||
require.True(t, ok)
|
||||
require.Len(t, groups, 1)
|
||||
group := groups[0]
|
||||
@ -329,10 +329,10 @@ func TestRouteGetRulesConfig(t *testing.T) {
|
||||
require.NoError(t, json.Unmarshal(response.Body(), result))
|
||||
require.NotNil(t, result)
|
||||
|
||||
require.Contains(t, *result, models.GetNamespaceKey(folder1.ParentUID, folder1.Title))
|
||||
require.Contains(t, *result, folder1.Fullpath)
|
||||
require.NotContains(t, *result, folder2.UID)
|
||||
|
||||
groups := (*result)[models.GetNamespaceKey(folder1.ParentUID, folder1.Title)]
|
||||
groups := (*result)[folder1.Fullpath]
|
||||
require.Len(t, groups, 1)
|
||||
require.Equal(t, group1Key.RuleGroup, groups[0].Name)
|
||||
require.Len(t, groups[0].Rules, len(group1))
|
||||
@ -361,7 +361,7 @@ func TestRouteGetRulesConfig(t *testing.T) {
|
||||
|
||||
models.RulesGroup(expectedRules).SortByGroupIndex()
|
||||
|
||||
groups, ok := (*result)[models.GetNamespaceKey(folder.ParentUID, folder.Title)]
|
||||
groups, ok := (*result)[folder.Fullpath]
|
||||
require.True(t, ok)
|
||||
require.Len(t, groups, 1)
|
||||
group := groups[0]
|
||||
|
@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
@ -84,9 +85,10 @@ func validGroup(cfg *setting.UnifiedAlertingSettings, rules ...apimodels.Postabl
|
||||
}
|
||||
|
||||
func randFolder() *folder.Folder {
|
||||
title := "TEST-FOLDER-" + util.GenerateShortUID()
|
||||
return &folder.Folder{
|
||||
UID: util.GenerateShortUID(),
|
||||
Title: "TEST-FOLDER-" + util.GenerateShortUID(),
|
||||
Title: title,
|
||||
// URL: "",
|
||||
// Version: 0,
|
||||
Created: time.Time{},
|
||||
@ -94,6 +96,8 @@ func randFolder() *folder.Folder {
|
||||
// UpdatedBy: 0,
|
||||
// CreatedBy: 0,
|
||||
// HasACL: false,
|
||||
ParentUID: uuid.NewString(),
|
||||
Fullpath: path.Join("parent-folder", title),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@ -18,7 +19,9 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
@ -32,6 +35,10 @@ import (
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
type folderService interface {
|
||||
GetNamespaceByUID(ctx context.Context, uid string, orgID int64, user identity.Requester) (*folder.Folder, error)
|
||||
}
|
||||
|
||||
type TestingApiSrv struct {
|
||||
*AlertingProxy
|
||||
DatasourceCache datasources.CacheService
|
||||
@ -43,22 +50,23 @@ type TestingApiSrv struct {
|
||||
featureManager featuremgmt.FeatureToggles
|
||||
appUrl *url.URL
|
||||
tracer tracing.Tracer
|
||||
folderService folderService
|
||||
}
|
||||
|
||||
// RouteTestGrafanaRuleConfig returns a list of potential alerts for a given rule configuration. This is intended to be
|
||||
// as true as possible to what would be generated by the ruler except that the resulting alerts are not filtered to
|
||||
// only Resolved / Firing and ready to send.
|
||||
func (srv TestingApiSrv) RouteTestGrafanaRuleConfig(c *contextmodel.ReqContext, body apimodels.PostableExtendedRuleNodeExtended) response.Response {
|
||||
folder, err := srv.folderService.GetNamespaceByUID(c.Req.Context(), body.NamespaceUID, c.OrgID, c.SignedInUser)
|
||||
if err != nil {
|
||||
return toNamespaceErrorResponse(dashboards.ErrFolderAccessDenied)
|
||||
}
|
||||
rule, err := validateRuleNode(
|
||||
&body.Rule,
|
||||
body.RuleGroup,
|
||||
srv.cfg.BaseInterval,
|
||||
c.SignedInUser.GetOrgID(),
|
||||
&folder.Folder{
|
||||
OrgID: c.SignedInUser.GetOrgID(),
|
||||
UID: body.NamespaceUID,
|
||||
Title: body.NamespaceTitle,
|
||||
},
|
||||
folder,
|
||||
srv.cfg,
|
||||
)
|
||||
if err != nil {
|
||||
@ -103,8 +111,7 @@ func (srv TestingApiSrv) RouteTestGrafanaRuleConfig(c *contextmodel.ReqContext,
|
||||
now,
|
||||
rule,
|
||||
results,
|
||||
// TODO remove when switched to full path https://github.com/grafana/grafana/issues/80324
|
||||
state.GetRuleExtraLabels(rule, ngmodels.GetNamespaceKey("", body.NamespaceTitle), includeFolder),
|
||||
state.GetRuleExtraLabels(rule, folder.Fullpath, includeFolder),
|
||||
)
|
||||
|
||||
alerts := make([]*amv2.PostableAlert, 0, len(transitions))
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@ -15,14 +16,17 @@ import (
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
acMock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
fakes "github.com/grafana/grafana/pkg/services/datasources/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval/eval_mocks"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
fakes2 "github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
@ -139,6 +143,36 @@ func TestRouteTestGrafanaRuleConfig(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("should return Forbidden if user cannot access folder", func(t *testing.T) {
|
||||
ac := acMock.New().WithPermissions([]ac.Permission{
|
||||
{Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceAllScope()},
|
||||
})
|
||||
|
||||
ruleStore := fakes2.NewRuleStore(t)
|
||||
ruleStore.Hook = func(cmd any) error {
|
||||
q, ok := cmd.(fakes2.GenericRecordedQuery)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if q.Name == "GetNamespaceByUID" {
|
||||
return dashboards.ErrFolderAccessDenied
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
srv := createTestingApiSrv(t, nil, ac, eval_mocks.NewEvaluatorFactory(&eval_mocks.ConditionEvaluatorMock{}), &featuremgmt.FeatureManager{}, ruleStore)
|
||||
|
||||
rule := validRule()
|
||||
|
||||
response := srv.RouteTestGrafanaRuleConfig(rc, definitions.PostableExtendedRuleNodeExtended{
|
||||
Rule: rule,
|
||||
NamespaceUID: uuid.NewString(),
|
||||
NamespaceTitle: "test-folder",
|
||||
})
|
||||
|
||||
require.Equal(t, http.StatusForbidden, response.Status())
|
||||
})
|
||||
|
||||
t.Run("should return Forbidden if user cannot query a data source", func(t *testing.T) {
|
||||
data1 := models.GenerateAlertQuery()
|
||||
data2 := models.GenerateAlertQuery()
|
||||
@ -147,15 +181,18 @@ func TestRouteTestGrafanaRuleConfig(t *testing.T) {
|
||||
{Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data1.DatasourceUID)},
|
||||
})
|
||||
|
||||
srv := createTestingApiSrv(t, nil, ac, eval_mocks.NewEvaluatorFactory(&eval_mocks.ConditionEvaluatorMock{}), &featuremgmt.FeatureManager{})
|
||||
f := randFolder()
|
||||
ruleStore := fakes2.NewRuleStore(t)
|
||||
ruleStore.Folders[rc.OrgID] = []*folder.Folder{f}
|
||||
srv := createTestingApiSrv(t, nil, ac, eval_mocks.NewEvaluatorFactory(&eval_mocks.ConditionEvaluatorMock{}), &featuremgmt.FeatureManager{}, ruleStore)
|
||||
|
||||
rule := validRule()
|
||||
rule.GrafanaManagedAlert.Data = ApiAlertQueriesFromAlertQueries([]models.AlertQuery{data1, data2})
|
||||
rule.GrafanaManagedAlert.Condition = data2.RefID
|
||||
response := srv.RouteTestGrafanaRuleConfig(rc, definitions.PostableExtendedRuleNodeExtended{
|
||||
Rule: rule,
|
||||
NamespaceUID: "test-folder",
|
||||
NamespaceTitle: "test-folder",
|
||||
NamespaceUID: f.UID,
|
||||
NamespaceTitle: f.Title,
|
||||
})
|
||||
|
||||
require.Equal(t, http.StatusForbidden, response.Status())
|
||||
@ -181,15 +218,19 @@ func TestRouteTestGrafanaRuleConfig(t *testing.T) {
|
||||
|
||||
evalFactory := eval_mocks.NewEvaluatorFactory(evaluator)
|
||||
|
||||
srv := createTestingApiSrv(t, ds, ac, evalFactory, &featuremgmt.FeatureManager{})
|
||||
f := randFolder()
|
||||
ruleStore := fakes2.NewRuleStore(t)
|
||||
ruleStore.Folders[rc.OrgID] = []*folder.Folder{f}
|
||||
|
||||
srv := createTestingApiSrv(t, ds, ac, evalFactory, &featuremgmt.FeatureManager{}, ruleStore)
|
||||
|
||||
rule := validRule()
|
||||
rule.GrafanaManagedAlert.Data = ApiAlertQueriesFromAlertQueries([]models.AlertQuery{data1, data2})
|
||||
rule.GrafanaManagedAlert.Condition = data2.RefID
|
||||
response := srv.RouteTestGrafanaRuleConfig(rc, definitions.PostableExtendedRuleNodeExtended{
|
||||
Rule: rule,
|
||||
NamespaceUID: "test-folder",
|
||||
NamespaceTitle: "test-folder",
|
||||
NamespaceUID: f.UID,
|
||||
NamespaceTitle: f.Title,
|
||||
})
|
||||
|
||||
require.Equal(t, http.StatusOK, response.Status())
|
||||
@ -256,7 +297,9 @@ func TestRouteEvalQueries(t *testing.T) {
|
||||
}
|
||||
evaluator.EXPECT().EvaluateRaw(mock.Anything, mock.Anything).Return(result, nil)
|
||||
|
||||
srv := createTestingApiSrv(t, ds, ac, eval_mocks.NewEvaluatorFactory(evaluator), &featuremgmt.FeatureManager{})
|
||||
ruleStore := fakes2.NewRuleStore(t)
|
||||
|
||||
srv := createTestingApiSrv(t, ds, ac, eval_mocks.NewEvaluatorFactory(evaluator), &featuremgmt.FeatureManager{}, ruleStore)
|
||||
|
||||
response := srv.RouteEvalQueries(rc, definitions.EvalQueriesPayload{
|
||||
Data: ApiAlertQueriesFromAlertQueries([]models.AlertQuery{data1, data2}),
|
||||
@ -316,7 +359,9 @@ func TestRouteEvalQueries(t *testing.T) {
|
||||
}
|
||||
evaluator.EXPECT().EvaluateRaw(mock.Anything, mock.Anything).Return(result, nil)
|
||||
|
||||
srv := createTestingApiSrv(t, ds, ac, eval_mocks.NewEvaluatorFactory(evaluator), featuremgmt.WithManager(featuremgmt.FlagAlertingQueryOptimization))
|
||||
ruleStore := fakes2.NewRuleStore(t)
|
||||
|
||||
srv := createTestingApiSrv(t, ds, ac, eval_mocks.NewEvaluatorFactory(evaluator), featuremgmt.WithManager(featuremgmt.FlagAlertingQueryOptimization), ruleStore)
|
||||
|
||||
response := srv.RouteEvalQueries(rc, definitions.EvalQueriesPayload{
|
||||
Data: ApiAlertQueriesFromAlertQueries(queries),
|
||||
@ -342,7 +387,7 @@ func TestRouteEvalQueries(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func createTestingApiSrv(t *testing.T, ds *fakes.FakeCacheService, ac *acMock.Mock, evaluator eval.EvaluatorFactory, featureManager *featuremgmt.FeatureManager) *TestingApiSrv {
|
||||
func createTestingApiSrv(t *testing.T, ds *fakes.FakeCacheService, ac *acMock.Mock, evaluator eval.EvaluatorFactory, featureManager *featuremgmt.FeatureManager, ruleStore RuleStore) *TestingApiSrv {
|
||||
if ac == nil {
|
||||
ac = acMock.New()
|
||||
}
|
||||
@ -354,5 +399,6 @@ func createTestingApiSrv(t *testing.T, ds *fakes.FakeCacheService, ac *acMock.Mo
|
||||
cfg: config(t),
|
||||
tracer: tracing.InitializeTracerForTest(),
|
||||
featureManager: featureManager,
|
||||
folderService: ruleStore,
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
@ -705,29 +704,3 @@ func GroupByAlertRuleGroupKey(rules []*AlertRule) map[AlertRuleGroupKey]RulesGro
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetNamespaceKey concatenates two strings with / as separator. If the latter string contains '/' it gets escaped with \/
|
||||
func GetNamespaceKey(parentUID, title string) string {
|
||||
if parentUID == "" {
|
||||
return title
|
||||
}
|
||||
b, err := json.Marshal([]string{parentUID, title})
|
||||
if err != nil {
|
||||
return title // this should not really happen
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// GetNamespaceTitleFromKey extracts the latter part from the string produced by GetNamespaceKey
|
||||
func GetNamespaceTitleFromKey(ns string) string {
|
||||
// the expected format of the string is a JSON array ["parentUID","title"]
|
||||
if !strings.HasPrefix(ns, "[") {
|
||||
return ns
|
||||
}
|
||||
var arr []string
|
||||
err := json.Unmarshal([]byte(ns), &arr)
|
||||
if err != nil || len(arr) != 2 {
|
||||
return ns
|
||||
}
|
||||
return arr[1]
|
||||
}
|
||||
|
@ -729,66 +729,3 @@ func TestTimeRangeYAML(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, yamlRaw, string(serialized))
|
||||
}
|
||||
|
||||
func TestGetNamespaceTitleFromKey(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"just title", "title with space", "title with space"},
|
||||
{"title and uid", `["parentUID","title"]`, "title"},
|
||||
{"wrong input-empty array", "[]", "[]"},
|
||||
{"wrong input-incorrect json", "[", "["},
|
||||
{"wrong input-long array", `["parentUID","title","title"]`, `["parentUID","title","title"]`},
|
||||
{"empty string", "", ""},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
actual := GetNamespaceTitleFromKey(tc.input)
|
||||
require.Equal(t, actual, tc.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNamespaceKey(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
parentUID string
|
||||
title string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "Parent UID and title",
|
||||
parentUID: "parentUID",
|
||||
title: "Title/Title",
|
||||
expected: `["parentUID","Title/Title"]`,
|
||||
},
|
||||
{
|
||||
name: "EmptyTitle",
|
||||
parentUID: "parentUID",
|
||||
title: "",
|
||||
expected: `["parentUID",""]`,
|
||||
},
|
||||
{
|
||||
name: "EmptyParentUID",
|
||||
parentUID: "",
|
||||
title: "Title",
|
||||
expected: "Title",
|
||||
},
|
||||
{
|
||||
name: "BothEmpty",
|
||||
parentUID: "",
|
||||
title: "",
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual := GetNamespaceKey(tt.parentUID, tt.title)
|
||||
require.Equal(t, actual, tt.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -512,8 +512,7 @@ func GetRuleExtraLabels(rule *models.AlertRule, folderTitle string, includeFolde
|
||||
extraLabels[alertingModels.RuleUIDLabel] = rule.UID
|
||||
|
||||
if includeFolder {
|
||||
// TODO remove when title will contain the full path https://github.com/grafana/grafana/issues/80324
|
||||
extraLabels[models.FolderTitleLabel] = models.GetNamespaceTitleFromKey(folderTitle)
|
||||
extraLabels[models.FolderTitleLabel] = folderTitle
|
||||
}
|
||||
return extraLabels
|
||||
}
|
||||
|
@ -7,18 +7,17 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"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/dashboards/dashboardaccess"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/search/model"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
|
||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
@ -434,58 +433,34 @@ func (st DBstore) GetRuleGroupInterval(ctx context.Context, orgID int64, namespa
|
||||
})
|
||||
}
|
||||
|
||||
// GetUserVisibleNamespaces returns the folders that are visible to the user and have at least one alert in it
|
||||
// GetUserVisibleNamespaces returns the folders that are visible to the user
|
||||
func (st DBstore) GetUserVisibleNamespaces(ctx context.Context, orgID int64, user identity.Requester) (map[string]*folder.Folder, error) {
|
||||
namespaceMap := make(map[string]*folder.Folder)
|
||||
|
||||
searchQuery := dashboards.FindPersistedDashboardsQuery{
|
||||
OrgId: orgID,
|
||||
folders, err := st.FolderService.GetFolders(ctx, folder.GetFoldersQuery{
|
||||
OrgID: orgID,
|
||||
WithFullpath: true,
|
||||
SignedInUser: user,
|
||||
Type: searchstore.TypeAlertFolder,
|
||||
Limit: -1,
|
||||
Permission: dashboardaccess.PERMISSION_VIEW,
|
||||
Sort: model.SortOption{},
|
||||
Filters: []any{
|
||||
searchstore.FolderWithAlertsFilter{},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var page int64 = 1
|
||||
for {
|
||||
query := searchQuery
|
||||
query.Page = page
|
||||
proj, err := st.DashboardService.FindDashboards(ctx, &query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(proj) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for _, hit := range proj {
|
||||
if !hit.IsFolder {
|
||||
continue
|
||||
}
|
||||
namespaceMap[hit.UID] = &folder.Folder{
|
||||
UID: hit.UID,
|
||||
Title: hit.Title,
|
||||
ParentUID: hit.FolderUID,
|
||||
}
|
||||
}
|
||||
page += 1
|
||||
namespaceMap := make(map[string]*folder.Folder)
|
||||
for _, f := range folders {
|
||||
namespaceMap[f.UID] = f
|
||||
}
|
||||
return namespaceMap, nil
|
||||
}
|
||||
|
||||
// GetNamespaceByUID is a handler for retrieving a namespace by its UID. Alerting rules follow a Grafana folder-like structure which we call namespaces.
|
||||
func (st DBstore) GetNamespaceByUID(ctx context.Context, uid string, orgID int64, user identity.Requester) (*folder.Folder, error) {
|
||||
folder, err := st.FolderService.Get(ctx, &folder.GetFolderQuery{OrgID: orgID, UID: &uid, SignedInUser: user})
|
||||
f, err := st.FolderService.GetFolders(ctx, folder.GetFoldersQuery{OrgID: orgID, UIDs: []string{uid}, WithFullpath: true, SignedInUser: user})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return folder, nil
|
||||
if len(f) == 0 {
|
||||
return nil, dashboards.ErrFolderAccessDenied
|
||||
}
|
||||
return f[0], nil
|
||||
}
|
||||
|
||||
func (st DBstore) GetAlertRulesKeysForScheduling(ctx context.Context) ([]ngmodels.AlertRuleKeyWithVersion, error) {
|
||||
@ -513,12 +488,6 @@ func (st DBstore) GetAlertRulesKeysForScheduling(ctx context.Context) ([]ngmodel
|
||||
|
||||
// GetAlertRulesForScheduling returns a short version of all alert rules except those that belong to an excluded list of organizations
|
||||
func (st DBstore) GetAlertRulesForScheduling(ctx context.Context, query *ngmodels.GetAlertRulesForSchedulingQuery) error {
|
||||
var folders []struct {
|
||||
OrgId int64
|
||||
Uid string
|
||||
Title string
|
||||
ParentUid string
|
||||
}
|
||||
var rules []*ngmodels.AlertRule
|
||||
return st.SQLStore.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
var disabledOrgs []int64
|
||||
@ -566,22 +535,36 @@ func (st DBstore) GetAlertRulesForScheduling(ctx context.Context, query *ngmodel
|
||||
query.ResultRules = rules
|
||||
|
||||
if query.PopulateFolders {
|
||||
foldersSql := sess.Table("folder").Alias("d").Select("d.org_id, d.uid, d.title, d.parent_uid").
|
||||
Where(`EXISTS (SELECT 1 FROM alert_rule a WHERE d.uid = a.namespace_uid AND d.org_id = a.org_id)`)
|
||||
if len(disabledOrgs) > 0 {
|
||||
foldersSql.NotIn("org_id", disabledOrgs)
|
||||
}
|
||||
|
||||
if err := foldersSql.Find(&folders); err != nil {
|
||||
return fmt.Errorf("failed to fetch a list of folders that contain alert rules: %w", err)
|
||||
}
|
||||
query.ResultFoldersTitles = make(map[ngmodels.FolderKey]string, len(folders))
|
||||
for _, f := range folders {
|
||||
key := ngmodels.FolderKey{
|
||||
OrgID: f.OrgId,
|
||||
UID: f.Uid,
|
||||
query.ResultFoldersTitles = map[ngmodels.FolderKey]string{}
|
||||
uids := map[int64]map[string]struct{}{}
|
||||
for _, r := range rules {
|
||||
om, ok := uids[r.OrgID]
|
||||
if !ok {
|
||||
om = make(map[string]struct{})
|
||||
uids[r.OrgID] = om
|
||||
}
|
||||
om[r.NamespaceUID] = struct{}{}
|
||||
}
|
||||
for orgID, uids := range uids {
|
||||
schedulerUser := accesscontrol.BackgroundUser("grafana_scheduler", orgID, org.RoleAdmin,
|
||||
[]accesscontrol.Permission{
|
||||
{
|
||||
Action: dashboards.ActionFoldersRead, Scope: dashboards.ScopeFoldersAll,
|
||||
},
|
||||
})
|
||||
|
||||
folders, err := st.FolderService.GetFolders(ctx, folder.GetFoldersQuery{
|
||||
OrgID: orgID,
|
||||
UIDs: maps.Keys(uids),
|
||||
WithFullpath: true,
|
||||
SignedInUser: schedulerUser,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch a list of folders that contain alert rules: %w", err)
|
||||
}
|
||||
for _, f := range folders {
|
||||
query.ResultFoldersTitles[ngmodels.FolderKey{OrgID: f.OrgID, UID: f.UID}] = f.Fullpath
|
||||
}
|
||||
query.ResultFoldersTitles[key] = ngmodels.GetNamespaceKey(f.ParentUid, f.Title)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"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"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
@ -44,7 +45,7 @@ func TestIntegrationUpdateAlertRules(t *testing.T) {
|
||||
store := &DBstore{
|
||||
SQLStore: sqlStore,
|
||||
Cfg: cfg.UnifiedAlerting,
|
||||
FolderService: setupFolderService(t, sqlStore, cfg),
|
||||
FolderService: setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures()),
|
||||
Logger: &logtest.Fake{},
|
||||
}
|
||||
generator := models.AlertRuleGen(withIntervalMatching(store.Cfg.BaseInterval), models.WithUniqueID())
|
||||
@ -98,7 +99,7 @@ func TestIntegrationUpdateAlertRulesWithUniqueConstraintViolation(t *testing.T)
|
||||
store := &DBstore{
|
||||
SQLStore: sqlStore,
|
||||
Cfg: cfg.UnifiedAlerting,
|
||||
FolderService: setupFolderService(t, sqlStore, cfg),
|
||||
FolderService: setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures()),
|
||||
Logger: &logtest.Fake{},
|
||||
}
|
||||
|
||||
@ -332,7 +333,7 @@ func TestIntegration_GetAlertRulesForScheduling(t *testing.T) {
|
||||
store := &DBstore{
|
||||
SQLStore: sqlStore,
|
||||
Cfg: cfg.UnifiedAlerting,
|
||||
FolderService: setupFolderService(t, sqlStore, cfg),
|
||||
FolderService: setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures()),
|
||||
FeatureToggles: featuremgmt.WithFeatures(),
|
||||
}
|
||||
|
||||
@ -341,9 +342,12 @@ func TestIntegration_GetAlertRulesForScheduling(t *testing.T) {
|
||||
rule2 := createRule(t, store, generator)
|
||||
|
||||
parentFolderUid := uuid.NewString()
|
||||
createFolder(t, store, parentFolderUid, "Very Parent Folder", rule1.OrgID, "")
|
||||
createFolder(t, store, rule1.NamespaceUID, rule1.Title, rule1.OrgID, parentFolderUid)
|
||||
createFolder(t, store, rule2.NamespaceUID, rule2.Title, rule2.OrgID, "")
|
||||
parentFolderTitle := "Very Parent Folder"
|
||||
createFolder(t, store, parentFolderUid, parentFolderTitle, rule1.OrgID, "")
|
||||
rule1FolderTitle := "folder-" + rule1.Title
|
||||
rule2FolderTitle := "folder-" + rule2.Title
|
||||
createFolder(t, store, rule1.NamespaceUID, rule1FolderTitle, rule1.OrgID, parentFolderUid)
|
||||
createFolder(t, store, rule2.NamespaceUID, rule2FolderTitle, rule2.OrgID, "")
|
||||
|
||||
createFolder(t, store, rule2.NamespaceUID, "same UID folder", generator().OrgID, "") // create a folder with the same UID but in the different org
|
||||
|
||||
@ -353,6 +357,7 @@ func TestIntegration_GetAlertRulesForScheduling(t *testing.T) {
|
||||
ruleGroups []string
|
||||
disabledOrgs []int64
|
||||
folders map[models.FolderKey]string
|
||||
flags []string
|
||||
}{
|
||||
{
|
||||
name: "without a rule group filter, it returns all created rules",
|
||||
@ -371,13 +376,13 @@ func TestIntegration_GetAlertRulesForScheduling(t *testing.T) {
|
||||
{
|
||||
name: "with populate folders enabled, it returns them",
|
||||
rules: []string{rule1.Title, rule2.Title},
|
||||
folders: map[models.FolderKey]string{rule1.GetFolderKey(): models.GetNamespaceKey(parentFolderUid, rule1.Title), rule2.GetFolderKey(): rule2.Title},
|
||||
folders: map[models.FolderKey]string{rule1.GetFolderKey(): rule1FolderTitle, rule2.GetFolderKey(): rule2FolderTitle},
|
||||
},
|
||||
{
|
||||
name: "with populate folders enabled and a filter on orgs, it only returns selected information",
|
||||
rules: []string{rule1.Title},
|
||||
disabledOrgs: []int64{rule2.OrgID},
|
||||
folders: map[models.FolderKey]string{rule1.GetFolderKey(): models.GetNamespaceKey(parentFolderUid, rule1.Title)},
|
||||
folders: map[models.FolderKey]string{rule1.GetFolderKey(): rule1FolderTitle},
|
||||
},
|
||||
}
|
||||
|
||||
@ -414,6 +419,20 @@ func TestIntegration_GetAlertRulesForScheduling(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("when nested folders are enabled folders should contain full path", func(t *testing.T) {
|
||||
store.FolderService = setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders))
|
||||
query := &models.GetAlertRulesForSchedulingQuery{
|
||||
PopulateFolders: true,
|
||||
}
|
||||
require.NoError(t, store.GetAlertRulesForScheduling(context.Background(), query))
|
||||
|
||||
expected := map[models.FolderKey]string{
|
||||
rule1.GetFolderKey(): parentFolderTitle + "/" + rule1FolderTitle,
|
||||
rule2.GetFolderKey(): rule2FolderTitle,
|
||||
}
|
||||
require.Equal(t, expected, query.ResultFoldersTitles)
|
||||
})
|
||||
}
|
||||
|
||||
func withIntervalMatching(baseInterval time.Duration) func(*models.AlertRule) {
|
||||
@ -430,7 +449,7 @@ func TestIntegration_CountAlertRules(t *testing.T) {
|
||||
|
||||
sqlStore := db.InitTestDB(t)
|
||||
cfg := setting.NewCfg()
|
||||
store := &DBstore{SQLStore: sqlStore, FolderService: setupFolderService(t, sqlStore, cfg)}
|
||||
store := &DBstore{SQLStore: sqlStore, FolderService: setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures())}
|
||||
rule := createRule(t, store, nil)
|
||||
|
||||
tests := map[string]struct {
|
||||
@ -479,7 +498,7 @@ func TestIntegration_DeleteInFolder(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
store := &DBstore{
|
||||
SQLStore: sqlStore,
|
||||
FolderService: setupFolderService(t, sqlStore, cfg),
|
||||
FolderService: setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures()),
|
||||
Logger: log.New("test-dbstore"),
|
||||
}
|
||||
rule := createRule(t, store, nil)
|
||||
@ -512,7 +531,7 @@ func TestIntegration_GetNamespaceByUID(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
store := &DBstore{
|
||||
SQLStore: sqlStore,
|
||||
FolderService: setupFolderService(t, sqlStore, cfg),
|
||||
FolderService: setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures()),
|
||||
Logger: log.New("test-dbstore"),
|
||||
}
|
||||
|
||||
@ -524,13 +543,36 @@ func TestIntegration_GetNamespaceByUID(t *testing.T) {
|
||||
}
|
||||
|
||||
uid := uuid.NewString()
|
||||
title := "folder-title"
|
||||
createFolder(t, store, uid, title, 1, "")
|
||||
parentUid := uuid.NewString()
|
||||
title := "folder/title"
|
||||
parentTitle := "parent-title"
|
||||
createFolder(t, store, parentUid, parentTitle, 1, "")
|
||||
createFolder(t, store, uid, title, 1, parentUid)
|
||||
|
||||
actual, err := store.GetNamespaceByUID(context.Background(), uid, 1, u)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, title, actual.Title)
|
||||
require.Equal(t, uid, actual.UID)
|
||||
require.Equal(t, title, actual.Fullpath)
|
||||
|
||||
t.Run("error when user does not have permissions", func(t *testing.T) {
|
||||
someUser := &user.SignedInUser{
|
||||
UserID: 2,
|
||||
OrgID: 1,
|
||||
OrgRole: org.RoleViewer,
|
||||
}
|
||||
_, err = store.GetNamespaceByUID(context.Background(), uid, 1, someUser)
|
||||
require.ErrorIs(t, err, dashboards.ErrFolderAccessDenied)
|
||||
})
|
||||
|
||||
t.Run("when nested folders are enabled full path should be populated with correct value", func(t *testing.T) {
|
||||
store.FolderService = setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders))
|
||||
actual, err := store.GetNamespaceByUID(context.Background(), uid, 1, u)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, title, actual.Title)
|
||||
require.Equal(t, uid, actual.UID)
|
||||
require.Equal(t, "parent-title/folder\\/title", actual.Fullpath)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationInsertAlertRules(t *testing.T) {
|
||||
@ -543,7 +585,7 @@ func TestIntegrationInsertAlertRules(t *testing.T) {
|
||||
cfg.UnifiedAlerting.BaseInterval = 1 * time.Second
|
||||
store := &DBstore{
|
||||
SQLStore: sqlStore,
|
||||
FolderService: setupFolderService(t, sqlStore, cfg),
|
||||
FolderService: setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures()),
|
||||
Logger: log.New("test-dbstore"),
|
||||
Cfg: cfg.UnifiedAlerting,
|
||||
}
|
||||
@ -630,11 +672,11 @@ func createFolder(t *testing.T, store *DBstore, uid, title string, orgID int64,
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func setupFolderService(t *testing.T, sqlStore *sqlstore.SQLStore, cfg *setting.Cfg) folder.Service {
|
||||
func setupFolderService(t *testing.T, sqlStore *sqlstore.SQLStore, cfg *setting.Cfg, features featuremgmt.FeatureToggles) folder.Service {
|
||||
tracer := tracing.InitializeTracerForTest()
|
||||
inProcBus := bus.ProvideBus(tracer)
|
||||
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
|
||||
_, dashboardStore := testutil.SetupDashboardService(t, sqlStore, folderStore, cfg)
|
||||
|
||||
return testutil.SetupFolderService(t, cfg, sqlStore, dashboardStore, folderStore, inProcBus)
|
||||
return testutil.SetupFolderService(t, cfg, sqlStore, dashboardStore, folderStore, inProcBus, features, &actest.FakeAccessControl{})
|
||||
}
|
||||
|
@ -247,12 +247,18 @@ func (f *RuleStore) GetUserVisibleNamespaces(_ context.Context, orgID int64, _ i
|
||||
return namespacesMap, nil
|
||||
}
|
||||
|
||||
func (f *RuleStore) GetNamespaceByUID(_ context.Context, uid string, orgID int64, _ identity.Requester) (*folder.Folder, error) {
|
||||
f.RecordedOps = append(f.RecordedOps, GenericRecordedQuery{
|
||||
func (f *RuleStore) GetNamespaceByUID(_ context.Context, uid string, orgID int64, user identity.Requester) (*folder.Folder, error) {
|
||||
q := GenericRecordedQuery{
|
||||
Name: "GetNamespaceByUID",
|
||||
Params: []any{orgID, uid},
|
||||
})
|
||||
|
||||
Params: []any{orgID, uid, user},
|
||||
}
|
||||
defer func() {
|
||||
f.RecordedOps = append(f.RecordedOps, q)
|
||||
}()
|
||||
err := f.Hook(q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
folders := f.Folders[orgID]
|
||||
for _, folder := range folders {
|
||||
if folder.UID == uid {
|
||||
|
@ -61,17 +61,18 @@ func SetupTestEnv(tb testing.TB, baseInterval time.Duration) (*ngalert.AlertNG,
|
||||
bus := bus.ProvideBus(tracer)
|
||||
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
|
||||
dashboardService, dashboardStore := testutil.SetupDashboardService(tb, sqlStore, folderStore, cfg)
|
||||
folderService := testutil.SetupFolderService(tb, cfg, sqlStore, dashboardStore, folderStore, bus)
|
||||
features := featuremgmt.WithFeatures()
|
||||
folderService := testutil.SetupFolderService(tb, cfg, sqlStore, dashboardStore, folderStore, bus, features, ac)
|
||||
ruleStore, err := store.ProvideDBStore(cfg, featuremgmt.WithFeatures(), sqlStore, folderService, &dashboards.FakeDashboardService{}, ac)
|
||||
require.NoError(tb, err)
|
||||
ng, err := ngalert.ProvideService(
|
||||
cfg, featuremgmt.WithFeatures(), nil, nil, routing.NewRouteRegister(), sqlStore, nil, nil, nil, quotatest.New(false, nil),
|
||||
cfg, features, nil, nil, routing.NewRouteRegister(), sqlStore, nil, nil, nil, quotatest.New(false, nil),
|
||||
secretsService, nil, m, folderService, ac, &dashboards.FakeDashboardService{}, nil, bus, ac,
|
||||
annotationstest.NewFakeAnnotationsRepo(), &pluginstore.FakePluginStore{}, tracer, ruleStore, migration.NewFakeMigrationService(tb), nil,
|
||||
)
|
||||
require.NoError(tb, err)
|
||||
return ng, &store.DBstore{
|
||||
FeatureToggles: ng.FeatureToggles,
|
||||
FeatureToggles: features,
|
||||
SQLStore: ng.SQLStore,
|
||||
Cfg: setting.UnifiedAlertingSettings{
|
||||
BaseInterval: baseInterval * time.Second,
|
||||
|
@ -24,12 +24,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func SetupFolderService(tb testing.TB, cfg *setting.Cfg, db db.DB, dashboardStore dashboards.Store, folderStore *folderimpl.DashboardFolderStoreImpl, bus *bus.InProcBus) folder.Service {
|
||||
func SetupFolderService(tb testing.TB, cfg *setting.Cfg, db db.DB, dashboardStore dashboards.Store, folderStore *folderimpl.DashboardFolderStoreImpl, bus *bus.InProcBus, features featuremgmt.FeatureToggles, ac accesscontrol.AccessControl) folder.Service {
|
||||
tb.Helper()
|
||||
|
||||
ac := acmock.New()
|
||||
features := featuremgmt.WithFeatures()
|
||||
|
||||
return folderimpl.ProvideService(ac, bus, cfg, dashboardStore, folderStore, db, features, nil)
|
||||
}
|
||||
|
||||
|
@ -403,8 +403,6 @@ func TestIntegrationAlertRuleNestedPermissions(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal(getGroup3Raw, &group3))
|
||||
|
||||
nestedKey := ngmodels.GetNamespaceKey("folder1", "subfolder")
|
||||
|
||||
expected := apimodels.NamespaceConfigResponse{
|
||||
"folder1": []apimodels.GettableRuleGroupConfig{
|
||||
group1,
|
||||
@ -412,7 +410,7 @@ func TestIntegrationAlertRuleNestedPermissions(t *testing.T) {
|
||||
"folder2": []apimodels.GettableRuleGroupConfig{
|
||||
group2,
|
||||
},
|
||||
nestedKey: []apimodels.GettableRuleGroupConfig{
|
||||
"folder1/subfolder": []apimodels.GettableRuleGroupConfig{
|
||||
group3,
|
||||
},
|
||||
}
|
||||
@ -446,7 +444,7 @@ func TestIntegrationAlertRuleNestedPermissions(t *testing.T) {
|
||||
assert.Equal(t, "folder2", rule.GrafanaManagedAlert.NamespaceUID)
|
||||
}
|
||||
|
||||
for _, rule := range allRules[nestedKey][0].Rules {
|
||||
for _, rule := range allRules["folder1/subfolder"][0].Rules {
|
||||
assert.Equal(t, "subfolder", rule.GrafanaManagedAlert.NamespaceUID)
|
||||
}
|
||||
})
|
||||
@ -468,7 +466,7 @@ func TestIntegrationAlertRuleNestedPermissions(t *testing.T) {
|
||||
rules, status, _ := apiClient.GetAllRulesGroupInFolderWithStatus(t, "subfolder")
|
||||
require.Equal(t, http.StatusAccepted, status)
|
||||
|
||||
nestedKey := ngmodels.GetNamespaceKey("folder1", "subfolder")
|
||||
nestedKey := "folder1/subfolder"
|
||||
require.Contains(t, rules, nestedKey)
|
||||
require.Len(t, rules[nestedKey], 1)
|
||||
require.Equal(t, allRules[nestedKey], rules[nestedKey])
|
||||
@ -602,7 +600,7 @@ func TestIntegrationAlertRuleNestedPermissions(t *testing.T) {
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
require.Contains(t, newAll, "folder1")
|
||||
require.NotContains(t, newAll, "folder2")
|
||||
require.Contains(t, newAll, ngmodels.GetNamespaceKey("folder1", "subfolder"))
|
||||
require.Contains(t, newAll, "folder1/subfolder")
|
||||
})
|
||||
|
||||
t.Run("Get by folder returns groups in folder", func(t *testing.T) {
|
||||
|
@ -51,6 +51,8 @@ func TestGrafanaRuleConfig(t *testing.T) {
|
||||
|
||||
apiCli := newAlertingApiClient(grafanaListedAddr, "admin", "admin")
|
||||
|
||||
apiCli.CreateFolder(t, "NamespaceUID", "NamespaceTitle")
|
||||
|
||||
dsCmd := &datasources.AddDataSourceCommand{
|
||||
Name: "TestDatasource",
|
||||
Type: "testdata",
|
||||
|
Loading…
Reference in New Issue
Block a user