refactor(unified-storage): move builders in their own package (#114375)

This commit is contained in:
Jean-Philippe Quéméner
2025-11-25 10:58:03 +01:00
committed by GitHub
parent a2f14d7306
commit b57e6383e4
36 changed files with 162 additions and 408 deletions
-47
View File
@@ -130,9 +130,6 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/RoaringBitmap/roaring v1.9.3 h1:t4EbC5qQwnisr5PrP9nt0IRhRTb9gMUgQF4t4S2OByM=
github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2JW2gggRdg=
github.com/RoaringBitmap/roaring/v2 v2.4.5/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f h1:HR5nRmUQgXrwqZOwZ2DAc/aCi3Bu3xENpspW935vxu0=
@@ -222,48 +219,10 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4=
github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/blevesearch/bleve/v2 v2.5.0 h1:HzYqBy/5/M9Ul9ESEmXzN/3Jl7YpmWBdHM/+zzv/3k4=
github.com/blevesearch/bleve/v2 v2.5.0/go.mod h1:PcJzTPnEynO15dCf9isxOga7YFRa/cMSsbnRwnszXUk=
github.com/blevesearch/bleve_index_api v1.2.7 h1:c8r9vmbaYQroAMSGag7zq5gEVPiuXrUQDqfnj7uYZSY=
github.com/blevesearch/bleve_index_api v1.2.7/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
github.com/blevesearch/geo v0.1.20 h1:paaSpu2Ewh/tn5DKn/FB5SzvH0EWupxHEIwbCk/QPqM=
github.com/blevesearch/geo v0.1.20/go.mod h1:DVG2QjwHNMFmjo+ZgzrIq2sfCh6rIHzy9d9d0B59I6w=
github.com/blevesearch/go-faiss v1.0.25 h1:lel1rkOUGbT1CJ0YgzKwC7k+XH0XVBHnCVWahdCXk4U=
github.com/blevesearch/go-faiss v1.0.25/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk=
github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
github.com/blevesearch/scorch_segment_api/v2 v2.3.9 h1:X6nJXnNHl7nasXW+U6y2Ns2Aw8F9STszkYkyBfQ+p0o=
github.com/blevesearch/scorch_segment_api/v2 v2.3.9/go.mod h1:IrzspZlVjhf4X29oJiEhBxEteTqOY9RlYlk1lCmYHr4=
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMGZzVrdmaozG2MfoB+A=
github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ=
github.com/blevesearch/vellum v1.1.0 h1:CinkGyIsgVlYf8Y2LUQHvdelgXr6PYuvoDIajq6yR9w=
github.com/blevesearch/vellum v1.1.0/go.mod h1:QgwWryE8ThtNPxtgWJof5ndPfx0/YMBh+W2weHKPw8Y=
github.com/blevesearch/zapx/v11 v11.4.1 h1:qFCPlFbsEdwbbckJkysptSQOsHn4s6ZOHL5GMAIAVHA=
github.com/blevesearch/zapx/v11 v11.4.1/go.mod h1:qNOGxIqdPC1MXauJCD9HBG487PxviTUUbmChFOAosGs=
github.com/blevesearch/zapx/v12 v12.4.1 h1:K77bhypII60a4v8mwvav7r4IxWA8qxhNjgF9xGdb9eQ=
github.com/blevesearch/zapx/v12 v12.4.1/go.mod h1:QRPrlPOzAxBNMI0MkgdD+xsTqx65zbuPr3Ko4Re49II=
github.com/blevesearch/zapx/v13 v13.4.1 h1:EnkEMZFUK0lsW/jOJJF2xOcp+W8TjEsyeN5BeAZEYYE=
github.com/blevesearch/zapx/v13 v13.4.1/go.mod h1:e6duBMlCvgbH9rkzNMnUa9hRI9F7ri2BRcHfphcmGn8=
github.com/blevesearch/zapx/v14 v14.4.1 h1:G47kGCshknBZzZAtjcnIAMn3oNx8XBLxp8DMq18ogyE=
github.com/blevesearch/zapx/v14 v14.4.1/go.mod h1:O7sDxiaL2r2PnCXbhh1Bvm7b4sP+jp4unE9DDPWGoms=
github.com/blevesearch/zapx/v15 v15.4.1 h1:B5IoTMUCEzFdc9FSQbhVOxAY+BO17c05866fNruiI7g=
github.com/blevesearch/zapx/v15 v15.4.1/go.mod h1:b/MreHjYeQoLjyY2+UaM0hGZZUajEbE0xhnr1A2/Q6Y=
github.com/blevesearch/zapx/v16 v16.2.2 h1:MifKJVRTEhMTgSlle2bDRTb39BGc9jXFRLPZc6r0Rzk=
github.com/blevesearch/zapx/v16 v16.2.2/go.mod h1:B9Pk4G1CqtErgQV9DyCSA9Lb7WZe4olYfGw7fVDZ4sk=
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
@@ -526,8 +485,6 @@ github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9v
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang-migrate/migrate/v4 v4.7.0 h1:gONcHxHApDTKXDyLH/H97gEHmpu1zcnnbAaq2zgrPrs=
github.com/golang-migrate/migrate/v4 v4.7.0/go.mod h1:Qvut3N4xKWjoH3sokBccML6WyHSnggXm/DvMMnTsQIc=
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -936,8 +893,6 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWu
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@@ -1184,8 +1139,6 @@ github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtC
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=
go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I=
go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM=
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo=
go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk=
@@ -23,7 +23,7 @@ import (
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
unisearch "github.com/grafana/grafana/pkg/storage/unified/search"
"github.com/grafana/grafana/pkg/storage/unified/search/builders"
)
type DashboardSearchClient struct {
@@ -37,11 +37,11 @@ func NewDashboardSearchClient(dashboardStore dashboards.Store, sorter sort.Servi
}
var sortByMapping = map[string]string{
unisearch.DASHBOARD_VIEWS_LAST_30_DAYS: "viewed-recently",
unisearch.DASHBOARD_VIEWS_TOTAL: "viewed",
unisearch.DASHBOARD_ERRORS_LAST_30_DAYS: "errors-recently",
unisearch.DASHBOARD_ERRORS_TOTAL: "errors",
"title": "alpha",
builders.DASHBOARD_VIEWS_LAST_30_DAYS: "viewed-recently",
builders.DASHBOARD_VIEWS_TOTAL: "viewed",
builders.DASHBOARD_ERRORS_LAST_30_DAYS: "errors-recently",
builders.DASHBOARD_ERRORS_TOTAL: "errors",
"title": "alpha",
}
func ParseSortName(sortName string) (string, bool, error) {
@@ -231,7 +231,7 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resourcepb.Reso
}
query.ManagerIdentity = vals[0]
case unisearch.DASHBOARD_LIBRARY_PANEL_REFERENCE:
case builders.DASHBOARD_LIBRARY_PANEL_REFERENCE:
if len(vals) != 1 {
return nil, apierrors.NewBadRequest("only one library panel uid is supported")
}
@@ -19,7 +19,7 @@ import (
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
unisearch "github.com/grafana/grafana/pkg/storage/unified/search"
"github.com/grafana/grafana/pkg/storage/unified/search/builders"
)
func TestDashboardSearchClient_Search(t *testing.T) {
@@ -130,7 +130,7 @@ func TestDashboardSearchClient_Search(t *testing.T) {
},
SortBy: []*resourcepb.ResourceSearchRequest_Sort{
{
Field: resource.SEARCH_FIELD_PREFIX + unisearch.DASHBOARD_VIEWS_TOTAL, // "fields." prefix should be removed
Field: resource.SEARCH_FIELD_PREFIX + builders.DASHBOARD_VIEWS_TOTAL, // "fields." prefix should be removed
Desc: false,
},
},
@@ -195,7 +195,7 @@ func TestDashboardSearchClient_Search(t *testing.T) {
},
SortBy: []*resourcepb.ResourceSearchRequest_Sort{
{
Field: unisearch.DASHBOARD_ERRORS_LAST_30_DAYS,
Field: builders.DASHBOARD_ERRORS_LAST_30_DAYS,
Desc: true,
},
},
@@ -560,7 +560,7 @@ func TestDashboardSearchClient_Search(t *testing.T) {
},
SortBy: []*resourcepb.ResourceSearchRequest_Sort{
{
Field: resource.SEARCH_FIELD_PREFIX + unisearch.DASHBOARD_VIEWS_TOTAL,
Field: resource.SEARCH_FIELD_PREFIX + builders.DASHBOARD_VIEWS_TOTAL,
},
},
}
@@ -591,7 +591,7 @@ func TestDashboardSearchClient_Search(t *testing.T) {
Key: dashboardKey,
Fields: []*resourcepb.Requirement{
{
Key: unisearch.DASHBOARD_LIBRARY_PANEL_REFERENCE,
Key: builders.DASHBOARD_LIBRARY_PANEL_REFERENCE,
Operator: "=",
Values: []string{"test-library-panel"},
},
@@ -633,7 +633,7 @@ func TestDashboardSearchClient_Search(t *testing.T) {
Key: dashboardKey,
Fields: []*resourcepb.Requirement{
{
Key: unisearch.DASHBOARD_LIBRARY_PANEL_REFERENCE,
Key: builders.DASHBOARD_LIBRARY_PANEL_REFERENCE,
Operator: "=",
Values: []string{"panel1", "panel2"},
},
@@ -652,7 +652,7 @@ func TestDashboardSearchClient_Search(t *testing.T) {
Key: dashboardKey,
Fields: []*resourcepb.Requirement{
{
Key: unisearch.DASHBOARD_LIBRARY_PANEL_REFERENCE,
Key: builders.DASHBOARD_LIBRARY_PANEL_REFERENCE,
Operator: "=",
Values: []string{"test-library-panel"},
},
@@ -679,7 +679,7 @@ func TestDashboardSearchClient_Search(t *testing.T) {
Key: dashboardKey,
Fields: []*resourcepb.Requirement{
{
Key: unisearch.DASHBOARD_LIBRARY_PANEL_REFERENCE,
Key: builders.DASHBOARD_LIBRARY_PANEL_REFERENCE,
Operator: "=",
Values: []string{"unused-library-panel"},
},
@@ -713,28 +713,28 @@ func TestParseSortName(t *testing.T) {
{
name: "viewed-recently with desc suffix",
sortName: "viewed-recently-desc",
wantField: unisearch.DASHBOARD_VIEWS_LAST_30_DAYS,
wantField: builders.DASHBOARD_VIEWS_LAST_30_DAYS,
wantDesc: true,
wantErr: false,
},
{
name: "defaults to desc",
sortName: "viewed",
wantField: unisearch.DASHBOARD_VIEWS_TOTAL,
wantField: builders.DASHBOARD_VIEWS_TOTAL,
wantDesc: true,
wantErr: false,
},
{
name: "errors-recentlyy with asc suffix",
sortName: "errors-recently-asc",
wantField: unisearch.DASHBOARD_ERRORS_LAST_30_DAYS,
wantField: builders.DASHBOARD_ERRORS_LAST_30_DAYS,
wantDesc: false,
wantErr: false,
},
{
name: "errors - defaults to desc too",
sortName: "errors",
wantField: unisearch.DASHBOARD_ERRORS_TOTAL,
wantField: builders.DASHBOARD_ERRORS_TOTAL,
wantDesc: true,
wantErr: false,
},
+3 -3
View File
@@ -32,7 +32,7 @@ import (
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/search"
"github.com/grafana/grafana/pkg/storage/unified/search/builders"
"github.com/grafana/grafana/pkg/util/errhttp"
)
@@ -406,7 +406,7 @@ func convertHttpSearchRequestToResourceSearchRequest(queryParams url.Values, use
// Add sorting
if queryParams.Has("sort") {
for _, sort := range queryParams["sort"] {
if slices.Contains(search.DashboardFields(), sort) {
if slices.Contains(builders.DashboardFields(), sort) {
sort = resource.SEARCH_FIELD_PREFIX + sort
}
s := &resourcepb.ResourceSearchRequest_Sort{Field: sort}
@@ -441,7 +441,7 @@ func convertHttpSearchRequestToResourceSearchRequest(queryParams url.Values, use
// The libraryPanel filter
if libraryPanel, ok := queryParams["libraryPanel"]; ok {
searchRequest.Options.Fields = append(searchRequest.Options.Fields, &resourcepb.Requirement{
Key: search.DASHBOARD_LIBRARY_PANEL_REFERENCE,
Key: builders.DASHBOARD_LIBRARY_PANEL_REFERENCE,
Operator: "=",
Values: libraryPanel,
})
+3 -3
View File
@@ -12,7 +12,7 @@ import (
"github.com/grafana/grafana/pkg/services/user"
res "github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/search"
"github.com/grafana/grafana/pkg/storage/unified/search/builders"
)
const (
@@ -129,9 +129,9 @@ func getColumns(fields []string) []*resourcepb.ResourceTableColumnDefinition {
for _, field := range fields {
switch field {
case "email":
columns = append(columns, search.TableColumnDefinitions[search.USER_EMAIL])
columns = append(columns, builders.TableColumnDefinitions[builders.USER_EMAIL])
case "login":
columns = append(columns, search.TableColumnDefinitions[search.USER_LOGIN])
columns = append(columns, builders.TableColumnDefinitions[builders.USER_LOGIN])
}
}
return columns
+4 -3
View File
@@ -265,6 +265,7 @@ import (
migrations2 "github.com/grafana/grafana/pkg/storage/unified/migrations"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/search"
"github.com/grafana/grafana/pkg/storage/unified/search/builders"
"github.com/grafana/grafana/pkg/storage/unified/sql"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor"
"github.com/grafana/grafana/pkg/tsdb/cloud-monitoring"
@@ -455,7 +456,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api
if err != nil {
return nil, err
}
ossDashboardStats := search.ProvideDashboardStats()
ossDashboardStats := builders.ProvideDashboardStats()
documentBuilderSupplier := search.ProvideDocumentBuilders(sqlStore, ossDashboardStats)
databaseDatabase := database4.ProvideDatabase(sqlStore, tracer)
clockClock := clock.ProvideClock()
@@ -1102,7 +1103,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac
if err != nil {
return nil, err
}
ossDashboardStats := search.ProvideDashboardStats()
ossDashboardStats := builders.ProvideDashboardStats()
documentBuilderSupplier := search.ProvideDocumentBuilders(sqlStore, ossDashboardStats)
databaseDatabase := database4.ProvideDatabase(sqlStore, tracer)
clockClock := clock.ProvideClock()
@@ -1757,7 +1758,7 @@ func InitializeDocumentBuilders(cfg *setting.Cfg) (resource.DocumentBuilderSuppl
if err != nil {
return nil, err
}
ossDashboardStats := search.ProvideDashboardStats()
ossDashboardStats := builders.ProvideDashboardStats()
documentBuilderSupplier := search.ProvideDocumentBuilders(sqlStore, ossDashboardStats)
return documentBuilderSupplier, nil
}
+3 -2
View File
@@ -66,6 +66,7 @@ import (
"github.com/grafana/grafana/pkg/storage/unified"
"github.com/grafana/grafana/pkg/storage/unified/resource"
search2 "github.com/grafana/grafana/pkg/storage/unified/search"
"github.com/grafana/grafana/pkg/storage/unified/search/builders"
"github.com/grafana/grafana/pkg/storage/unified/sql"
)
@@ -138,8 +139,8 @@ var wireExtsBasicSet = wire.NewSet(
wire.Bind(new(auth.IDSigner), new(*idimpl.LocalSigner)),
manager.ProvideInstaller,
wire.Bind(new(plugins.Installer), new(*manager.PluginInstaller)),
search2.ProvideDashboardStats,
wire.Bind(new(search2.DashboardStats), new(*search2.OssDashboardStats)),
builders.ProvideDashboardStats,
wire.Bind(new(builders.DashboardStats), new(*builders.OssDashboardStats)),
search2.ProvideDocumentBuilders,
sandbox.ProvideService,
wire.Bind(new(sandbox.Sandbox), new(*sandbox.Service)),
@@ -57,7 +57,7 @@ import (
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/search"
"github.com/grafana/grafana/pkg/storage/unified/search/builders"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/util/retryer"
)
@@ -479,7 +479,7 @@ func (dr *DashboardServiceImpl) GetDashboardsByLibraryPanelUID(ctx context.Conte
Options: &resourcepb.ListOptions{
Fields: []*resourcepb.Requirement{
{
Key: search.DASHBOARD_LIBRARY_PANEL_REFERENCE,
Key: builders.DASHBOARD_LIBRARY_PANEL_REFERENCE,
Operator: string(selection.Equals),
Values: []string{libraryPanelUID},
},
@@ -46,7 +46,7 @@ import (
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/search"
"github.com/grafana/grafana/pkg/storage/unified/search/builders"
"github.com/grafana/grafana/pkg/tests/testsuite"
"github.com/grafana/grafana/pkg/util/testutil"
)
@@ -2654,7 +2654,7 @@ func TestGetDashboardsByLibraryPanelUID(t *testing.T) {
k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.MatchedBy(func(req *resourcepb.ResourceSearchRequest) bool {
return len(req.Options.Fields) == 1 &&
req.Options.Fields[0].Key == search.DASHBOARD_LIBRARY_PANEL_REFERENCE &&
req.Options.Fields[0].Key == builders.DASHBOARD_LIBRARY_PANEL_REFERENCE &&
req.Options.Fields[0].Values[0] == "test-library-panel"
})).Return(searchResponse, nil).Once()
@@ -7,7 +7,7 @@ import (
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/search"
"github.com/grafana/grafana/pkg/storage/unified/search/builders"
)
// regression test - parsing int32 values from search results was causing a panic
@@ -25,11 +25,11 @@ func TestParseResults(t *testing.T) {
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: search.DASHBOARD_ERRORS_LAST_1_DAYS,
Name: builders.DASHBOARD_ERRORS_LAST_1_DAYS,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
},
{
Name: search.DASHBOARD_LINK_COUNT,
Name: builders.DASHBOARD_LINK_COUNT,
Type: resourcepb.ResourceTableColumnDefinition_INT32,
},
{
@@ -75,11 +75,11 @@ func TestParseResults(t *testing.T) {
Type: resourcepb.ResourceTableColumnDefinition_STRING,
},
{
Name: search.DASHBOARD_ERRORS_LAST_1_DAYS,
Name: builders.DASHBOARD_ERRORS_LAST_1_DAYS,
Type: resourcepb.ResourceTableColumnDefinition_INT64,
},
{
Name: search.DASHBOARD_LINK_COUNT,
Name: builders.DASHBOARD_LINK_COUNT,
Type: resourcepb.ResourceTableColumnDefinition_INT32,
},
{
+6 -3
View File
@@ -32,6 +32,7 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/search/builders"
authlib "github.com/grafana/authlib/types"
@@ -1178,10 +1179,11 @@ func (b *bleveIndex) toBleveSearchRequest(ctx context.Context, req *resourcepb.R
facets[f.Field] = bleve.NewFacetRequest(f.Field, int(f.Limit))
}
// Convert resource-specific fields to bleve fields (just considers dashboard fields for now)
// Convert resource-specific fields to bleve fields.
// TODO: use b.fields.Field(f) instead of builders.DashboardFields() to avoid dashboard-specific code in search server.
fields := make([]string, 0, len(req.Fields))
for _, f := range req.Fields {
if slices.Contains(DashboardFields(), f) {
if slices.Contains(builders.DashboardFields(), f) {
f = resource.SEARCH_FIELD_PREFIX + f
}
fields = append(fields, f)
@@ -1534,7 +1536,8 @@ func getSortFields(req *resourcepb.ResourceSearchRequest) []string {
input = field
}
if slices.Contains(DashboardFields(), input) {
// TODO: pass fields parameter and use fields.Field(input) instead of builders.DashboardFields() to avoid dashboard-specific code.
if slices.Contains(builders.DashboardFields(), input) {
input = resource.SEARCH_FIELD_PREFIX + input
}
@@ -16,6 +16,7 @@ import (
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/search"
"github.com/grafana/grafana/pkg/storage/unified/search/builders"
)
const threshold = 9999
@@ -265,8 +266,8 @@ func newTestDashboardsIndex(t testing.TB, threshold int64, size int64, writer re
ctx := identity.WithRequester(context.Background(), &user.SignedInUser{Namespace: "ns"})
info, err := search.DashboardBuilder(func(ctx context.Context, namespace string, blob resource.BlobSupport) (resource.DocumentBuilder, error) {
return &search.DashboardDocumentBuilder{
info, err := builders.DashboardBuilder(func(ctx context.Context, namespace string, blob resource.BlobSupport) (resource.DocumentBuilder, error) {
return &builders.DashboardDocumentBuilder{
Namespace: namespace,
Blob: blob,
Stats: make(map[string]map[string]int64), // empty stats
+15 -14
View File
@@ -32,6 +32,7 @@ import (
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/search/builders"
)
// This verifies that we close all indexes properly and shutdown all background goroutines from our tests.
@@ -76,8 +77,8 @@ func testBleveBackend(t *testing.T, backend *bleveBackend) {
t.Run("build dashboards", func(t *testing.T) {
key := dashboardskey
info, err := DashboardBuilder(func(ctx context.Context, namespace string, blob resource.BlobSupport) (resource.DocumentBuilder, error) {
return &DashboardDocumentBuilder{
info, err := builders.DashboardBuilder(func(ctx context.Context, namespace string, blob resource.BlobSupport) (resource.DocumentBuilder, error) {
return &builders.DashboardDocumentBuilder{
Namespace: namespace,
Blob: blob,
Stats: make(map[string]map[string]int64), // empty stats
@@ -107,9 +108,9 @@ func testBleveBackend(t *testing.T, backend *bleveBackend) {
Title: "aaa (dash)",
Folder: "xxx",
Fields: map[string]any{
DASHBOARD_PANEL_TYPES: []string{"timeseries", "table"},
DASHBOARD_ERRORS_TODAY: 25,
DASHBOARD_VIEWS_LAST_1_DAYS: 50,
builders.DASHBOARD_PANEL_TYPES: []string{"timeseries", "table"},
builders.DASHBOARD_ERRORS_TODAY: 25,
builders.DASHBOARD_VIEWS_LAST_1_DAYS: 50,
},
Labels: map[string]string{
utils.LabelKeyDeprecatedInternalID: "10", // nolint:staticcheck
@@ -140,9 +141,9 @@ func testBleveBackend(t *testing.T, backend *bleveBackend) {
Title: "bbb (dash)",
Folder: "xxx",
Fields: map[string]any{
DASHBOARD_PANEL_TYPES: []string{"timeseries"},
DASHBOARD_ERRORS_TODAY: 40,
DASHBOARD_VIEWS_LAST_1_DAYS: 100,
builders.DASHBOARD_PANEL_TYPES: []string{"timeseries"},
builders.DASHBOARD_ERRORS_TODAY: 40,
builders.DASHBOARD_VIEWS_LAST_1_DAYS: 100,
},
Tags: []string{"aa"},
Labels: map[string]string{
@@ -271,15 +272,15 @@ func testBleveBackend(t *testing.T, backend *bleveBackend) {
Key: key,
},
Limit: 100000,
Fields: []string{DASHBOARD_ERRORS_TODAY, DASHBOARD_VIEWS_LAST_1_DAYS, "fieldThatDoesntExist"},
Fields: []string{builders.DASHBOARD_ERRORS_TODAY, builders.DASHBOARD_VIEWS_LAST_1_DAYS, "fieldThatDoesntExist"},
SortBy: []*resourcepb.ResourceSearchRequest_Sort{
{Field: "fields." + DASHBOARD_VIEWS_LAST_1_DAYS, Desc: true},
{Field: "fields." + builders.DASHBOARD_VIEWS_LAST_1_DAYS, Desc: true},
},
}, nil, nil)
require.NoError(t, err)
require.Equal(t, 2, len(rsp.Results.Columns))
require.Equal(t, DASHBOARD_ERRORS_TODAY, rsp.Results.Columns[0].Name)
require.Equal(t, DASHBOARD_VIEWS_LAST_1_DAYS, rsp.Results.Columns[1].Name)
require.Equal(t, builders.DASHBOARD_ERRORS_TODAY, rsp.Results.Columns[0].Name)
require.Equal(t, builders.DASHBOARD_VIEWS_LAST_1_DAYS, rsp.Results.Columns[1].Name)
// sorted descending so should start with highest dashboard_views_last_1_days (100)
val, err := resource.DecodeCell(rsp.Results.Columns[1], 0, rsp.Results.Rows[0].Cells[1])
require.NoError(t, err)
@@ -291,9 +292,9 @@ func testBleveBackend(t *testing.T, backend *bleveBackend) {
Key: key,
},
Limit: 100000,
Fields: []string{DASHBOARD_ERRORS_TODAY, DASHBOARD_VIEWS_LAST_1_DAYS, "fieldThatDoesntExist"},
Fields: []string{builders.DASHBOARD_ERRORS_TODAY, builders.DASHBOARD_VIEWS_LAST_1_DAYS, "fieldThatDoesntExist"},
SortBy: []*resourcepb.ResourceSearchRequest_Sort{
{Field: "fields." + DASHBOARD_VIEWS_LAST_1_DAYS, Desc: true},
{Field: "fields." + builders.DASHBOARD_VIEWS_LAST_1_DAYS, Desc: true},
},
}, nil, nil)
require.NoError(t, err)
@@ -1,4 +1,4 @@
package search
package builders
import (
"bytes"
@@ -15,10 +15,6 @@ import (
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
//------------------------------------------------------------
// Standard dashboard fields
//------------------------------------------------------------
const DASHBOARD_SCHEMA_VERSION = "schema_version"
const DASHBOARD_LINK_COUNT = "link_count"
const DASHBOARD_PANEL_TYPES = "panel_types"
@@ -26,10 +22,6 @@ const DASHBOARD_DS_TYPES = "ds_types"
const DASHBOARD_TRANSFORMATIONS = "transformation"
const DASHBOARD_LIBRARY_PANEL_REFERENCE = "reference.LibraryPanel"
//------------------------------------------------------------
// The following fields are added in enterprise
//------------------------------------------------------------
const DASHBOARD_VIEWS_LAST_1_DAYS = "views_last_1_days"
const DASHBOARD_VIEWS_LAST_7_DAYS = "views_last_7_days"
const DASHBOARD_VIEWS_LAST_30_DAYS = "views_last_30_days"
@@ -268,7 +260,6 @@ func (s *DashboardDocumentBuilder) BuildDocument(ctx context.Context, key *resou
if err != nil {
return nil, err
}
// metadata name is the dashboard uid
summary.UID = obj.GetName()
summary.ID = obj.GetDeprecatedInternalID() // nolint:staticcheck
@@ -331,7 +322,6 @@ func (s *DashboardDocumentBuilder) BuildDocument(ctx context.Context, key *resou
doc.Fields[DASHBOARD_TRANSFORMATIONS] = transformations
}
// Add the stats fields
for k, v := range s.Stats[summary.UID] {
doc.Fields[k] = v
}
@@ -1,4 +1,4 @@
package search
package builders
import (
"context"
@@ -0,0 +1,66 @@
package builders
import (
"context"
claims "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/store/kind/dashboard"
"github.com/grafana/grafana/pkg/storage/unified/resource"
)
// All returns all document builders from this package.
// These builders have dependencies on Grafana apps (dashboard and user).
func All(sql db.DB, sprinkles DashboardStats) ([]resource.DocumentBuilderInfo, error) {
dashboards, err := DashboardBuilder(func(ctx context.Context, namespace string, blob resource.BlobSupport) (resource.DocumentBuilder, error) {
logger := log.New("dashboard_builder", "namespace", namespace)
dsinfo := []*dashboard.DatasourceQueryResult{{}}
ns, err := claims.ParseNamespace(namespace)
if err != nil && sql != nil {
rows, err := sql.GetSqlxSession().Query(ctx, "SELECT uid,type,name,is_default FROM data_source WHERE org_id=?", ns.OrgID)
if err != nil {
return nil, err
}
defer func() {
_ = rows.Close()
}()
for rows.Next() {
info := &dashboard.DatasourceQueryResult{}
err = rows.Scan(&info.UID, &info.Type, &info.Name, &info.IsDefault)
if err != nil {
return nil, err
}
dsinfo = append(dsinfo, info)
}
}
var stats map[string]map[string]int64
if sprinkles != nil {
stats, err = sprinkles.GetStats(ctx, namespace)
if err != nil {
logger.Warn("Failed to get sprinkles", "error", err)
}
}
return &DashboardDocumentBuilder{
Namespace: namespace,
Blob: blob,
Stats: stats,
DatasourceLookup: dashboard.CreateDatasourceLookup(dsinfo),
}, nil
})
if err != nil {
return nil, err
}
users, err := GetUserBuilder()
if err != nil {
return nil, err
}
return []resource.DocumentBuilderInfo{dashboards, users}, nil
}
@@ -1,4 +1,4 @@
package search_test
package builders
import (
"context"
@@ -14,7 +14,6 @@ import (
"github.com/grafana/grafana/pkg/services/store/kind/dashboard"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/search"
)
func doSnapshotTests(t *testing.T, builder resource.DocumentBuilder, kind string, key *resourcepb.ResourceKey, names []string) {
@@ -36,7 +35,6 @@ func doSnapshotTests(t *testing.T, builder resource.DocumentBuilder, kind string
outpath := filepath.Join("testdata", "doc", prefix+"-out.json")
// test path
// nolint:gosec
expect, _ := os.ReadFile(outpath)
if !assert.JSONEq(t, string(expect), string(out)) {
@@ -47,6 +45,19 @@ func doSnapshotTests(t *testing.T, builder resource.DocumentBuilder, kind string
}
}
func TestUserDocumentBuilder(t *testing.T) {
info, err := GetUserBuilder()
require.NoError(t, err)
doSnapshotTests(t, info.Builder, "user", &resourcepb.ResourceKey{
Namespace: "default",
Group: "iam.grafana.app",
Resource: "users",
}, []string{
"user-with-login-and-email",
"user-with-login-only",
})
}
func TestDashboardDocumentBuilder(t *testing.T) {
key := &resourcepb.ResourceKey{
Namespace: "default",
@@ -54,18 +65,18 @@ func TestDashboardDocumentBuilder(t *testing.T) {
Resource: "dashboards",
}
info, err := search.DashboardBuilder(func(ctx context.Context, namespace string, blob resource.BlobSupport) (resource.DocumentBuilder, error) {
return &search.DashboardDocumentBuilder{
info, err := DashboardBuilder(func(ctx context.Context, namespace string, blob resource.BlobSupport) (resource.DocumentBuilder, error) {
return &DashboardDocumentBuilder{
Namespace: namespace,
Blob: blob,
Stats: map[string]map[string]int64{
"aaa": {
search.DASHBOARD_ERRORS_LAST_1_DAYS: 1,
search.DASHBOARD_ERRORS_LAST_7_DAYS: 1,
DASHBOARD_ERRORS_LAST_1_DAYS: 1,
DASHBOARD_ERRORS_LAST_7_DAYS: 1,
},
},
DatasourceLookup: dashboard.CreateDatasourceLookup([]*dashboard.DatasourceQueryResult{{
Name: "TheDisplayName", // used to be the unique ID!
Name: "TheDisplayName",
Type: "my-custom-plugin",
UID: "DSUID",
}}),
@@ -76,12 +87,10 @@ func TestDashboardDocumentBuilder(t *testing.T) {
builder, err := info.Namespaced(context.Background(), key.Namespace, nil)
require.NoError(t, err)
// Dashboards (custom)
doSnapshotTests(t, builder, "dashboard", key, []string{
"aaa",
})
// Standard
builder = resource.StandardDocumentBuilder()
doSnapshotTests(t, builder, "folder", &resourcepb.ResourceKey{
Namespace: "default",
@@ -1,4 +1,4 @@
package search
package builders
import (
"bytes"
@@ -54,7 +54,6 @@ var _ resource.DocumentBuilder = new(userDocumentBuilder)
type userDocumentBuilder struct{}
// BuildDocument implements resource.DocumentBuilder.
func (u *userDocumentBuilder) BuildDocument(ctx context.Context, key *resourcepb.ResourceKey, rv int64, value []byte) (*resource.IndexableDocument, error) {
user := &iamv0.User{}
err := json.NewDecoder(bytes.NewReader(value)).Decode(user)
+9 -64
View File
@@ -1,87 +1,32 @@
package search
import (
"context"
claims "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/store/kind/dashboard"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/search/builders"
)
// The default list of open source document builders
// StandardDocumentBuilders provides the default list of document builders for open source Grafana.
// It combines the standard document builder with external builders for dashboards and users.
type StandardDocumentBuilders struct {
sql db.DB
sprinkles DashboardStats
sprinkles builders.DashboardStats
}
// Hooked up so wire can fill in different sprinkles
func ProvideDocumentBuilders(sql db.DB, sprinkles DashboardStats) resource.DocumentBuilderSupplier {
func ProvideDocumentBuilders(sql db.DB, sprinkles builders.DashboardStats) resource.DocumentBuilderSupplier {
return &StandardDocumentBuilders{sql, sprinkles}
}
func (s *StandardDocumentBuilders) GetDocumentBuilders() ([]resource.DocumentBuilderInfo, error) {
dashboards, err := DashboardBuilder(func(ctx context.Context, namespace string, blob resource.BlobSupport) (resource.DocumentBuilder, error) {
logger := log.New("dashboard_builder", "namespace", namespace)
dsinfo := []*dashboard.DatasourceQueryResult{{}}
ns, err := claims.ParseNamespace(namespace)
if err != nil && s.sql != nil {
rows, err := s.sql.GetSqlxSession().Query(ctx, "SELECT uid,type,name,is_default FROM data_source WHERE org_id=?", ns.OrgID)
if err != nil {
return nil, err
}
defer func() {
_ = rows.Close()
}()
for rows.Next() {
info := &dashboard.DatasourceQueryResult{}
err = rows.Scan(&info.UID, &info.Type, &info.Name, &info.IsDefault)
if err != nil {
return nil, err
}
dsinfo = append(dsinfo, info)
}
}
// Fetch dashboard sprinkles for the namespace
// This could take a while if namespace has a lot of dashboards
var stats map[string]map[string]int64
if s.sprinkles != nil {
stats, err = s.sprinkles.GetStats(ctx, namespace)
if err != nil {
// only log a warning. Don't need to fail the indexer if we can't get sprinkles
logger.Warn("Failed to get sprinkles", "error", err)
}
}
return &DashboardDocumentBuilder{
Namespace: namespace,
Blob: blob,
Stats: stats,
DatasourceLookup: dashboard.CreateDatasourceLookup(dsinfo),
}, nil
})
all, err := builders.All(s.sql, s.sprinkles)
if err != nil {
return nil, err
}
users, err := GetUserBuilder()
if err != nil {
return nil, err
}
return []resource.DocumentBuilderInfo{
// The default builder
result := []resource.DocumentBuilderInfo{
{
Builder: resource.StandardDocumentBuilder(),
},
// Dashboard builder
dashboards,
// User builder
users,
}, err
}
return append(result, all...), nil
}
-215
View File
@@ -1,215 +0,0 @@
package search_test
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/selection"
iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/search"
)
func TestUserDocumentBuilder(t *testing.T) {
info, err := search.GetUserBuilder()
require.NoError(t, err)
doSnapshotTests(t, info.Builder, "user", &resourcepb.ResourceKey{
Namespace: "default",
Group: "iam.grafana.app",
Resource: "users",
}, []string{
"user-with-login-and-email",
"user-with-login-only",
})
}
func TestUserSearch(t *testing.T) {
key := resource.NamespacedResource{
Namespace: "default",
Group: iamv0.UserResourceInfo.GroupResource().Group,
Resource: iamv0.UserResourceInfo.GroupResource().Resource,
}
index := newTestUsersIndex(t, 100, 2, func(index resource.ResourceIndex) (int64, error) {
return 0, nil
})
users := []iamv0.User{
{
ObjectMeta: metav1.ObjectMeta{
Name: "user1",
Namespace: "default",
},
Spec: iamv0.UserSpec{
Login: "user.one",
Email: "user.one@test.com",
Role: "Viewer",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "user2",
Namespace: "default",
},
Spec: iamv0.UserSpec{
Login: "user.two",
Email: "user.two@test.com",
Role: "Viewer",
},
},
}
indexUserDocuments(t, index, key, users)
// Sanity check - title search
checkUserSearchQuery(t, index, newTestsUserQueryWithTitle(key, "user2"), []string{"user2"})
t.Run("can search users by login", func(t *testing.T) {
// Search by login
checkUserSearchQuery(t, index, newTestUserQueryWithReqs(key, []*resourcepb.Requirement{
{
Key: "fields.login",
Operator: string(selection.Equals),
Values: []string{"user.one"},
},
}), []string{"user1"})
checkUserSearchQuery(t, index, newTestUserQueryWithReqs(key, []*resourcepb.Requirement{
{
Key: "fields.login",
Operator: string(selection.Equals),
Values: []string{"user.two"},
},
}), []string{"user2"})
})
t.Run("can search users by wildcard login", func(t *testing.T) {
checkUserSearchQuery(t, index, newTestUserQueryWithReqs(key, []*resourcepb.Requirement{
{
Key: "fields.login",
Operator: string(selection.Equals),
Values: []string{"user.*"},
},
}), []string{"user1", "user2"})
})
t.Run("can search users by email", func(t *testing.T) {
// Search by email
checkUserSearchQuery(t, index, newTestUserQueryWithReqs(key, []*resourcepb.Requirement{
{
Key: "fields.email",
Operator: string(selection.Equals),
Values: []string{"user.one@test.com"},
},
}), []string{"user1"})
checkUserSearchQuery(t, index, newTestUserQueryWithReqs(key, []*resourcepb.Requirement{
{
Key: "fields.email",
Operator: string(selection.Equals),
Values: []string{"user.two@test.com"},
},
}), []string{"user2"})
})
}
func newTestUsersIndex(t testing.TB, threshold int64, size int64, writer resource.BuildFn) resource.ResourceIndex {
t.Helper()
gr := iamv0.UserResourceInfo.GroupResource()
key := &resourcepb.ResourceKey{
Namespace: "default",
Group: gr.Group,
Resource: gr.Resource,
}
backend, err := search.NewBleveBackend(search.BleveOptions{
Root: t.TempDir(),
FileThreshold: threshold, // use in-memory for tests
}, tracing.NewNoopTracerService(), nil)
require.NoError(t, err)
t.Cleanup(backend.Stop)
ctx := identity.WithRequester(context.Background(), &user.SignedInUser{Namespace: "ns"})
info, err := search.GetUserBuilder()
require.NoError(t, err)
index, err := backend.BuildIndex(ctx, resource.NamespacedResource{
Namespace: key.Namespace,
Group: key.Group,
Resource: key.Resource,
}, size, info.Fields, "test", writer, nil, false)
require.NoError(t, err)
return index
}
func indexUserDocuments(t *testing.T, index resource.ResourceIndex, key resource.NamespacedResource, users []iamv0.User) {
t.Helper()
items := make([]*resource.BulkIndexItem, 0, len(users))
for _, user := range users {
items = append(items, &resource.BulkIndexItem{
Action: resource.ActionIndex,
Doc: &resource.IndexableDocument{
RV: 1,
Name: user.Name,
Key: &resourcepb.ResourceKey{
Name: user.Name,
Namespace: key.Namespace,
Group: key.Group,
Resource: key.Resource,
},
Title: user.Name,
Fields: map[string]any{search.USER_LOGIN: user.Spec.Login, search.USER_EMAIL: user.Spec.Email},
},
})
}
req := &resource.BulkIndexRequest{Items: items}
require.NoError(t, index.BulkIndex(req))
}
func checkUserSearchQuery(t *testing.T, index resource.ResourceIndex, query *resourcepb.ResourceSearchRequest, orderedExpectedNames []string) {
t.Helper()
res, err := index.Search(context.Background(), nil, query, nil, nil)
require.NoError(t, err)
require.Equal(t, int64(len(orderedExpectedNames)), res.TotalHits)
names := make([]string, len(res.Results.Rows))
for ix, row := range res.Results.Rows {
names[ix] = row.Key.Name
}
assert.ElementsMatch(t, orderedExpectedNames, names)
}
func newTestUserQueryWithReqs(key resource.NamespacedResource, filterReqs []*resourcepb.Requirement) *resourcepb.ResourceSearchRequest {
return &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Namespace: key.Namespace,
Group: key.Group,
Resource: key.Resource,
},
Fields: filterReqs,
},
Limit: 100,
}
}
func newTestsUserQueryWithTitle(key resource.NamespacedResource, title string) *resourcepb.ResourceSearchRequest {
return &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Namespace: key.Namespace,
Group: key.Group,
Resource: key.Resource,
},
},
Query: title,
Limit: 100,
}
}