mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
UnifiedSearch: Introduce a ResourceIndex interface and bleve implementation (#96826)
Co-authored-by: Scott Lepper <scott.lepper@gmail.com>
This commit is contained in:
parent
bbae396db4
commit
c6848d4b68
15
go.mod
15
go.mod
@ -38,6 +38,7 @@ require (
|
|||||||
github.com/beevik/etree v1.4.1 // @grafana/grafana-backend-group
|
github.com/beevik/etree v1.4.1 // @grafana/grafana-backend-group
|
||||||
github.com/benbjohnson/clock v1.3.5 // @grafana/alerting-backend
|
github.com/benbjohnson/clock v1.3.5 // @grafana/alerting-backend
|
||||||
github.com/blang/semver/v4 v4.0.0 // indirect; @grafana/grafana-developer-enablement-squad
|
github.com/blang/semver/v4 v4.0.0 // indirect; @grafana/grafana-developer-enablement-squad
|
||||||
|
github.com/blevesearch/bleve/v2 v2.4.3 // @grafana/grafana-search-and-storage
|
||||||
github.com/blugelabs/bluge v0.1.9 // @grafana/grafana-backend-group
|
github.com/blugelabs/bluge v0.1.9 // @grafana/grafana-backend-group
|
||||||
github.com/blugelabs/bluge_segment_api v0.2.0 // @grafana/grafana-backend-group
|
github.com/blugelabs/bluge_segment_api v0.2.0 // @grafana/grafana-backend-group
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // @grafana/grafana-backend-group
|
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // @grafana/grafana-backend-group
|
||||||
@ -480,11 +481,24 @@ require github.com/openzipkin/zipkin-go v0.4.3 // @grafana/oss-big-tent
|
|||||||
require (
|
require (
|
||||||
cloud.google.com/go/longrunning v0.6.0 // indirect
|
cloud.google.com/go/longrunning v0.6.0 // indirect
|
||||||
github.com/at-wat/mqtt-go v0.19.4 // indirect
|
github.com/at-wat/mqtt-go v0.19.4 // indirect
|
||||||
|
github.com/blevesearch/bleve_index_api v1.1.12 // indirect
|
||||||
|
github.com/blevesearch/geo v0.1.20 // indirect
|
||||||
|
github.com/blevesearch/go-faiss v1.0.23 // indirect
|
||||||
|
github.com/blevesearch/gtreap v0.1.1 // indirect
|
||||||
|
github.com/blevesearch/scorch_segment_api/v2 v2.2.16 // indirect
|
||||||
|
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
|
||||||
|
github.com/blevesearch/zapx/v11 v11.3.10 // indirect
|
||||||
|
github.com/blevesearch/zapx/v12 v12.3.10 // indirect
|
||||||
|
github.com/blevesearch/zapx/v13 v13.3.10 // indirect
|
||||||
|
github.com/blevesearch/zapx/v14 v14.3.10 // indirect
|
||||||
|
github.com/blevesearch/zapx/v15 v15.3.16 // indirect
|
||||||
|
github.com/blevesearch/zapx/v16 v16.1.8 // indirect
|
||||||
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 // indirect
|
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 // indirect
|
||||||
github.com/cespare/xxhash v1.1.0 // indirect
|
github.com/cespare/xxhash v1.1.0 // indirect
|
||||||
github.com/dolthub/maphash v0.1.0 // indirect
|
github.com/dolthub/maphash v0.1.0 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
github.com/gammazero/deque v0.2.1 // indirect
|
github.com/gammazero/deque v0.2.1 // indirect
|
||||||
|
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
||||||
github.com/grafana/jsonparser v0.0.0-20240425183733-ea80629e1a32 // indirect
|
github.com/grafana/jsonparser v0.0.0-20240425183733-ea80629e1a32 // indirect
|
||||||
github.com/grafana/loki/pkg/push v0.0.0-20231124142027-e52380921608 // indirect
|
github.com/grafana/loki/pkg/push v0.0.0-20231124142027-e52380921608 // indirect
|
||||||
github.com/grafana/sqlds/v4 v4.1.0 // indirect
|
github.com/grafana/sqlds/v4 v4.1.0 // indirect
|
||||||
@ -500,6 +514,7 @@ require (
|
|||||||
github.com/sercand/kuberesolver/v5 v5.1.1 // indirect
|
github.com/sercand/kuberesolver/v5 v5.1.1 // indirect
|
||||||
github.com/shadowspore/fossil-delta v0.0.0-20240102155221-e3a8590b820b // indirect
|
github.com/shadowspore/fossil-delta v0.0.0-20240102155221-e3a8590b820b // indirect
|
||||||
github.com/sony/gobreaker v0.5.0 // indirect
|
github.com/sony/gobreaker v0.5.0 // indirect
|
||||||
|
go.etcd.io/bbolt v1.3.10 // indirect
|
||||||
go.opentelemetry.io/collector/featuregate v1.9.0 // indirect
|
go.opentelemetry.io/collector/featuregate v1.9.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 // indirect
|
||||||
go4.org/netipx v0.0.0-20230125063823-8449b0a6169f // indirect
|
go4.org/netipx v0.0.0-20230125063823-8449b0a6169f // indirect
|
||||||
|
28
go.sum
28
go.sum
@ -1616,19 +1616,45 @@ github.com/bits-and-blooms/bitset v1.12.0 h1:U/q1fAF7xXRhFCrhROzIfffYnu+dlS38vCZ
|
|||||||
github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||||
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
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/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||||
|
github.com/blevesearch/bleve/v2 v2.4.3 h1:XDYj+1prgX84L2Cf+V3ojrOPqXxy0qxyd2uLMmeuD+4=
|
||||||
|
github.com/blevesearch/bleve/v2 v2.4.3/go.mod h1:hEPDPrbYw3vyrm5VOa36GyS4bHWuIf4Fflp7460QQXY=
|
||||||
|
github.com/blevesearch/bleve_index_api v1.1.12 h1:P4bw9/G/5rulOF7SJ9l4FsDoo7UFJ+5kexNy1RXfegY=
|
||||||
|
github.com/blevesearch/bleve_index_api v1.1.12/go.mod h1:PbcwjIcRmjhGbkS/lJCpfgVSMROV6TRubGGAODaK1W8=
|
||||||
|
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.23 h1:Wmc5AFwDLKGl2L6mjLX1Da3vCL0EKa2uHHSorcIS1Uc=
|
||||||
|
github.com/blevesearch/go-faiss v1.0.23/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
|
||||||
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
|
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/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.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+7LMvAB5IbSA=
|
github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+7LMvAB5IbSA=
|
||||||
github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
|
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/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
|
||||||
|
github.com/blevesearch/scorch_segment_api/v2 v2.2.16 h1:uGvKVvG7zvSxCwcm4/ehBa9cCEuZVE+/zvrSl57QUVY=
|
||||||
|
github.com/blevesearch/scorch_segment_api/v2 v2.2.16/go.mod h1:VF5oHVbIFTu+znY1v30GjSpT5+9YFs9dV2hjvuh34F0=
|
||||||
github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ=
|
github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ=
|
||||||
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
|
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/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 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
|
||||||
github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
|
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.0.5/go.mod h1:atE0EH3fvk43zzS7t1YNdNC7DbmcC3uz+eMD5xZ2OyQ=
|
github.com/blevesearch/vellum v1.0.5/go.mod h1:atE0EH3fvk43zzS7t1YNdNC7DbmcC3uz+eMD5xZ2OyQ=
|
||||||
github.com/blevesearch/vellum v1.0.10 h1:HGPJDT2bTva12hrHepVT3rOyIKFFF4t7Gf6yMxyMIPI=
|
github.com/blevesearch/vellum v1.0.10 h1:HGPJDT2bTva12hrHepVT3rOyIKFFF4t7Gf6yMxyMIPI=
|
||||||
github.com/blevesearch/vellum v1.0.10/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k=
|
github.com/blevesearch/vellum v1.0.10/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k=
|
||||||
|
github.com/blevesearch/zapx/v11 v11.3.10 h1:hvjgj9tZ9DeIqBCxKhi70TtSZYMdcFn7gDb71Xo/fvk=
|
||||||
|
github.com/blevesearch/zapx/v11 v11.3.10/go.mod h1:0+gW+FaE48fNxoVtMY5ugtNHHof/PxCqh7CnhYdnMzQ=
|
||||||
|
github.com/blevesearch/zapx/v12 v12.3.10 h1:yHfj3vXLSYmmsBleJFROXuO08mS3L1qDCdDK81jDl8s=
|
||||||
|
github.com/blevesearch/zapx/v12 v12.3.10/go.mod h1:0yeZg6JhaGxITlsS5co73aqPtM04+ycnI6D1v0mhbCs=
|
||||||
|
github.com/blevesearch/zapx/v13 v13.3.10 h1:0KY9tuxg06rXxOZHg3DwPJBjniSlqEgVpxIqMGahDE8=
|
||||||
|
github.com/blevesearch/zapx/v13 v13.3.10/go.mod h1:w2wjSDQ/WBVeEIvP0fvMJZAzDwqwIEzVPnCPrz93yAk=
|
||||||
|
github.com/blevesearch/zapx/v14 v14.3.10 h1:SG6xlsL+W6YjhX5N3aEiL/2tcWh3DO75Bnz77pSwwKU=
|
||||||
|
github.com/blevesearch/zapx/v14 v14.3.10/go.mod h1:qqyuR0u230jN1yMmE4FIAuCxmahRQEOehF78m6oTgns=
|
||||||
|
github.com/blevesearch/zapx/v15 v15.3.16 h1:Ct3rv7FUJPfPk99TI/OofdC+Kpb4IdyfdMH48sb+FmE=
|
||||||
|
github.com/blevesearch/zapx/v15 v15.3.16/go.mod h1:Turk/TNRKj9es7ZpKK95PS7f6D44Y7fAFy8F4LXQtGg=
|
||||||
|
github.com/blevesearch/zapx/v16 v16.1.8 h1:Bxzpw6YQpFs7UjoCV1+RvDw6fmAT2GZxldwX8b3wVBM=
|
||||||
|
github.com/blevesearch/zapx/v16 v16.1.8/go.mod h1:JqQlOqlRVaYDkpLIl3JnKql8u4zKTNlVEa3nLsi0Gn8=
|
||||||
github.com/blugelabs/bluge v0.1.9 h1:bPgXlcsWugrXNjzeoLdOnvfJpHsyODKpYaAndayl/SM=
|
github.com/blugelabs/bluge v0.1.9 h1:bPgXlcsWugrXNjzeoLdOnvfJpHsyODKpYaAndayl/SM=
|
||||||
github.com/blugelabs/bluge v0.1.9/go.mod h1:5d7LktUkQgvbh5Bmi6tPWtvo4+6uRTm6gAwP+5z6FqQ=
|
github.com/blugelabs/bluge v0.1.9/go.mod h1:5d7LktUkQgvbh5Bmi6tPWtvo4+6uRTm6gAwP+5z6FqQ=
|
||||||
github.com/blugelabs/bluge_segment_api v0.2.0 h1:cCX1Y2y8v0LZ7+EEJ6gH7dW6TtVTW4RhG0vp3R+N2Lo=
|
github.com/blugelabs/bluge_segment_api v0.2.0 h1:cCX1Y2y8v0LZ7+EEJ6gH7dW6TtVTW4RhG0vp3R+N2Lo=
|
||||||
@ -2073,6 +2099,8 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2V
|
|||||||
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||||
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||||
|
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/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
||||||
github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=
|
github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=
|
||||||
|
12
go.work.sum
12
go.work.sum
@ -571,6 +571,7 @@ github.com/RoaringBitmap/gocroaring v0.4.0 h1:5nufXUgWpBEUNEJXw7926YAA58ZAQRpWPr
|
|||||||
github.com/RoaringBitmap/real-roaring-datasets v0.0.0-20190726190000-eb7c87156f76 h1:ZYlhPbqQFU+AHfgtCdHGDTtRW1a8geZyiE8c6Q+Sl1s=
|
github.com/RoaringBitmap/real-roaring-datasets v0.0.0-20190726190000-eb7c87156f76 h1:ZYlhPbqQFU+AHfgtCdHGDTtRW1a8geZyiE8c6Q+Sl1s=
|
||||||
github.com/RoaringBitmap/roaring v0.9.4 h1:ckvZSX5gwCRaJYBNe7syNawCU5oruY9gQmjXlp4riwo=
|
github.com/RoaringBitmap/roaring v0.9.4 h1:ckvZSX5gwCRaJYBNe7syNawCU5oruY9gQmjXlp4riwo=
|
||||||
github.com/RoaringBitmap/roaring v0.9.4/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA=
|
github.com/RoaringBitmap/roaring v0.9.4/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA=
|
||||||
|
github.com/RoaringBitmap/roaring v1.2.3/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE=
|
||||||
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
|
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
|
||||||
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0=
|
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0=
|
||||||
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM=
|
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM=
|
||||||
@ -651,11 +652,13 @@ github.com/benbjohnson/immutable v0.4.0/go.mod h1:iAr8OjJGLnLmVUr9MZ/rz4PWUy6Ouc
|
|||||||
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
|
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
|
||||||
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
|
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
|
||||||
github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
|
github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
|
||||||
|
github.com/blevesearch/bleve_index_api v1.0.6/go.mod h1:YXMDwaXFFXwncRS8UobWs7nvo0DmusriM1nztTlj1ms=
|
||||||
github.com/blevesearch/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:kDy+zgJFJJoJYBvdfBSiZYBbdsUL0XcjHYWezpQBGPA=
|
github.com/blevesearch/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:kDy+zgJFJJoJYBvdfBSiZYBbdsUL0XcjHYWezpQBGPA=
|
||||||
github.com/blevesearch/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:9eJDeqxJ3E7WnLebQUlPD7ZjSce7AnDb9vjGmMCbD0A=
|
github.com/blevesearch/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:9eJDeqxJ3E7WnLebQUlPD7ZjSce7AnDb9vjGmMCbD0A=
|
||||||
github.com/blevesearch/goleveldb v1.0.1 h1:iAtV2Cu5s0GD1lwUiekkFHe2gTMCCNVj2foPclDLIFI=
|
github.com/blevesearch/goleveldb v1.0.1 h1:iAtV2Cu5s0GD1lwUiekkFHe2gTMCCNVj2foPclDLIFI=
|
||||||
github.com/blevesearch/goleveldb v1.0.1/go.mod h1:WrU8ltZbIp0wAoig/MHbrPCXSOLpe79nz5lv5nqfYrQ=
|
github.com/blevesearch/goleveldb v1.0.1/go.mod h1:WrU8ltZbIp0wAoig/MHbrPCXSOLpe79nz5lv5nqfYrQ=
|
||||||
github.com/blevesearch/mmap-go v1.0.3/go.mod h1:pYvKl/grLQrBxuaRYgoTssa4rVujYYeenDp++2E+yvs=
|
github.com/blevesearch/mmap-go v1.0.3/go.mod h1:pYvKl/grLQrBxuaRYgoTssa4rVujYYeenDp++2E+yvs=
|
||||||
|
github.com/blevesearch/scorch_segment_api/v2 v2.1.6/go.mod h1:nQQYlp51XvoSVxcciBjtvuHPIVjlWrN1hX4qwK2cqdc=
|
||||||
github.com/blevesearch/snowball v0.6.1 h1:cDYjn/NCH+wwt2UdehaLpr2e4BwLIjN4V/TdLsL+B5A=
|
github.com/blevesearch/snowball v0.6.1 h1:cDYjn/NCH+wwt2UdehaLpr2e4BwLIjN4V/TdLsL+B5A=
|
||||||
github.com/blevesearch/snowball v0.6.1/go.mod h1:ZF0IBg5vgpeoUhnMza2v0A/z8m1cWPlwhke08LpNusg=
|
github.com/blevesearch/snowball v0.6.1/go.mod h1:ZF0IBg5vgpeoUhnMza2v0A/z8m1cWPlwhke08LpNusg=
|
||||||
github.com/blevesearch/stempel v0.2.0 h1:CYzVPaScODMvgE9o+kf6D4RJ/VRomyi9uHF+PtB+Afc=
|
github.com/blevesearch/stempel v0.2.0 h1:CYzVPaScODMvgE9o+kf6D4RJ/VRomyi9uHF+PtB+Afc=
|
||||||
@ -978,8 +981,6 @@ github.com/grafana/go-json v0.0.0-20241106155216-71a03f133f5c/go.mod h1:oq7eo15S
|
|||||||
github.com/grafana/gomemcache v0.0.0-20240229205252-cd6a66d6fb56/go.mod h1:PGk3RjYHpxMM8HFPhKKo+vve3DdlPUELZLSDEFehPuU=
|
github.com/grafana/gomemcache v0.0.0-20240229205252-cd6a66d6fb56/go.mod h1:PGk3RjYHpxMM8HFPhKKo+vve3DdlPUELZLSDEFehPuU=
|
||||||
github.com/grafana/grafana-app-sdk v0.19.0/go.mod h1:y0BgzYxc+a7CwOqkwUhN9zXd5cgZJjd2zAbgHEd/xzo=
|
github.com/grafana/grafana-app-sdk v0.19.0/go.mod h1:y0BgzYxc+a7CwOqkwUhN9zXd5cgZJjd2zAbgHEd/xzo=
|
||||||
github.com/grafana/pyroscope-go/godeltaprof v0.1.6/go.mod h1:Tk376Nbldo4Cha9RgiU7ik8WKFkNpfds98aUzS8omLE=
|
github.com/grafana/pyroscope-go/godeltaprof v0.1.6/go.mod h1:Tk376Nbldo4Cha9RgiU7ik8WKFkNpfds98aUzS8omLE=
|
||||||
github.com/grafana/pyroscope/api v1.0.0 h1:RWK3kpv8EAnB7JpOqnf//xwE84DdKF03N/iFxpFAoHY=
|
|
||||||
github.com/grafana/pyroscope/api v1.0.0/go.mod h1:CUrgOgSZDnx4M1mlRoxhrVKkTuKIse9p4FtuPbrGA04=
|
|
||||||
github.com/grafana/tail v0.0.0-20230510142333-77b18831edf0 h1:bjh0PVYSVVFxzINqPFYJmAmJNrWPgnVjuSdYJGHmtFU=
|
github.com/grafana/tail v0.0.0-20230510142333-77b18831edf0 h1:bjh0PVYSVVFxzINqPFYJmAmJNrWPgnVjuSdYJGHmtFU=
|
||||||
github.com/grafana/tail v0.0.0-20230510142333-77b18831edf0/go.mod h1:7t5XR+2IA8P2qggOAHTj/GCZfoLBle3OvNSYh1VkRBU=
|
github.com/grafana/tail v0.0.0-20230510142333-77b18831edf0/go.mod h1:7t5XR+2IA8P2qggOAHTj/GCZfoLBle3OvNSYh1VkRBU=
|
||||||
github.com/grafana/thema v0.0.0-20230511182720-3146087fcc26 h1:HX927q4X1n451pnGb8U0wq74i8PCzuxVjzv7TyD10kc=
|
github.com/grafana/thema v0.0.0-20230511182720-3146087fcc26 h1:HX927q4X1n451pnGb8U0wq74i8PCzuxVjzv7TyD10kc=
|
||||||
@ -1061,6 +1062,7 @@ github.com/jon-whit/go-grpc-prometheus v1.4.0 h1:/wmpGDJcLXuEjXryWhVYEGt9YBRhtLw
|
|||||||
github.com/jon-whit/go-grpc-prometheus v1.4.0/go.mod h1:iTPm+Iuhh3IIqR0iGZ91JJEg5ax6YQEe1I0f6vtBuao=
|
github.com/jon-whit/go-grpc-prometheus v1.4.0/go.mod h1:iTPm+Iuhh3IIqR0iGZ91JJEg5ax6YQEe1I0f6vtBuao=
|
||||||
github.com/joncrlsn/dque v0.0.0-20211108142734-c2ef48c5192a h1:sfe532Ipn7GX0V6mHdynBk393rDmqgI0QmjLK7ct7TU=
|
github.com/joncrlsn/dque v0.0.0-20211108142734-c2ef48c5192a h1:sfe532Ipn7GX0V6mHdynBk393rDmqgI0QmjLK7ct7TU=
|
||||||
github.com/joncrlsn/dque v0.0.0-20211108142734-c2ef48c5192a/go.mod h1:dNKs71rs2VJGBAmttu7fouEsRQlRjxy0p1Sx+T5wbpY=
|
github.com/joncrlsn/dque v0.0.0-20211108142734-c2ef48c5192a/go.mod h1:dNKs71rs2VJGBAmttu7fouEsRQlRjxy0p1Sx+T5wbpY=
|
||||||
|
github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
|
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
|
||||||
github.com/jsternberg/zap-logfmt v1.2.0 h1:1v+PK4/B48cy8cfQbxL4FmmNZrjnIMr2BsnyEmXqv2o=
|
github.com/jsternberg/zap-logfmt v1.2.0 h1:1v+PK4/B48cy8cfQbxL4FmmNZrjnIMr2BsnyEmXqv2o=
|
||||||
github.com/jsternberg/zap-logfmt v1.2.0/go.mod h1:kz+1CUmCutPWABnNkOu9hOHKdT2q3TDYCcsFy9hpqb0=
|
github.com/jsternberg/zap-logfmt v1.2.0/go.mod h1:kz+1CUmCutPWABnNkOu9hOHKdT2q3TDYCcsFy9hpqb0=
|
||||||
@ -1293,8 +1295,6 @@ github.com/opentracing/basictracer-go v1.0.0 h1:YyUAhaEfjoWXclZVJ9sGoNct7j4TVk7l
|
|||||||
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5 h1:ZCnq+JUrvXcDVhX/xRolRBZifmabN1HcS1wrPSvxhrU=
|
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5 h1:ZCnq+JUrvXcDVhX/xRolRBZifmabN1HcS1wrPSvxhrU=
|
||||||
github.com/openzipkin/zipkin-go v0.4.2 h1:zjqfqHjUpPmB3c1GlCvvgsM1G4LkvqQbBDueDOCg/jA=
|
github.com/openzipkin/zipkin-go v0.4.2 h1:zjqfqHjUpPmB3c1GlCvvgsM1G4LkvqQbBDueDOCg/jA=
|
||||||
github.com/openzipkin/zipkin-go v0.4.2/go.mod h1:ZeVkFjuuBiSy13y8vpSDCjMi9GoI3hPpCJSBx/EYFhY=
|
github.com/openzipkin/zipkin-go v0.4.2/go.mod h1:ZeVkFjuuBiSy13y8vpSDCjMi9GoI3hPpCJSBx/EYFhY=
|
||||||
github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg=
|
|
||||||
github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c=
|
|
||||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
|
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
|
||||||
github.com/oschwald/geoip2-golang v1.11.0 h1:hNENhCn1Uyzhf9PTmquXENiWS6AlxAEnBII6r8krA3w=
|
github.com/oschwald/geoip2-golang v1.11.0 h1:hNENhCn1Uyzhf9PTmquXENiWS6AlxAEnBII6r8krA3w=
|
||||||
github.com/oschwald/geoip2-golang v1.11.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo=
|
github.com/oschwald/geoip2-golang v1.11.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo=
|
||||||
@ -1384,6 +1384,7 @@ github.com/shoenig/test v1.7.1 h1:UJcjSAI3aUKx52kfcfhblgyhZceouhvvs3OYdWgn+PY=
|
|||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||||
github.com/sony/gobreaker v0.4.1 h1:oMnRNZXX5j85zso6xCPRNPtmAycat+WcoKbklScLDgQ=
|
github.com/sony/gobreaker v0.4.1 h1:oMnRNZXX5j85zso6xCPRNPtmAycat+WcoKbklScLDgQ=
|
||||||
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
|
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
|
||||||
|
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc=
|
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc=
|
||||||
github.com/stoewer/parquet-cli v0.0.7 h1:rhdZODIbyMS3twr4OM3am8BPPT5pbfMcHLH93whDM5o=
|
github.com/stoewer/parquet-cli v0.0.7 h1:rhdZODIbyMS3twr4OM3am8BPPT5pbfMcHLH93whDM5o=
|
||||||
@ -1473,8 +1474,6 @@ github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd
|
|||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow=
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow=
|
||||||
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=
|
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=
|
||||||
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
|
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
|
||||||
github.com/yalue/merged_fs v1.3.0 h1:qCeh9tMPNy/i8cwDsQTJ5bLr6IRxbs6meakNE5O+wyY=
|
|
||||||
github.com/yalue/merged_fs v1.3.0/go.mod h1:WqqchfVYQyclV2tnR7wtRhBddzBvLVR83Cjw9BKQw0M=
|
|
||||||
github.com/ydb-platform/ydb-go-genproto v0.0.0-20240126124512-dbb0e1720dbf h1:ckwNHVo4bv2tqNkgx3W3HANh3ta1j6TR5qw08J1A7Tw=
|
github.com/ydb-platform/ydb-go-genproto v0.0.0-20240126124512-dbb0e1720dbf h1:ckwNHVo4bv2tqNkgx3W3HANh3ta1j6TR5qw08J1A7Tw=
|
||||||
github.com/ydb-platform/ydb-go-genproto v0.0.0-20240126124512-dbb0e1720dbf/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I=
|
github.com/ydb-platform/ydb-go-genproto v0.0.0-20240126124512-dbb0e1720dbf/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I=
|
||||||
github.com/ydb-platform/ydb-go-genproto v0.0.0-20240528144234-5d5a685e41f7 h1:nL8XwD6fSst7xFUirkaWJmE7kM0CdWRYgu6+YQer1d4=
|
github.com/ydb-platform/ydb-go-genproto v0.0.0-20240528144234-5d5a685e41f7 h1:nL8XwD6fSst7xFUirkaWJmE7kM0CdWRYgu6+YQer1d4=
|
||||||
@ -1498,6 +1497,7 @@ github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wK
|
|||||||
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs=
|
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs=
|
||||||
go.einride.tech/aip v0.67.1 h1:d/4TW92OxXBngkSOwWS2CH5rez869KpKMaN44mdxkFI=
|
go.einride.tech/aip v0.67.1 h1:d/4TW92OxXBngkSOwWS2CH5rez869KpKMaN44mdxkFI=
|
||||||
go.einride.tech/aip v0.67.1/go.mod h1:ZGX4/zKw8dcgzdLsrvpOOGxfxI2QSk12SlP7d6c0/XI=
|
go.einride.tech/aip v0.67.1/go.mod h1:ZGX4/zKw8dcgzdLsrvpOOGxfxI2QSk12SlP7d6c0/XI=
|
||||||
|
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||||
go.etcd.io/gofail v0.1.0 h1:XItAMIhOojXFQMgrxjnd2EIIHun/d5qL0Pf7FzVTkFg=
|
go.etcd.io/gofail v0.1.0 h1:XItAMIhOojXFQMgrxjnd2EIIHun/d5qL0Pf7FzVTkFg=
|
||||||
go.etcd.io/gofail v0.1.0/go.mod h1:VZBCXYGZhHAinaBiiqYvuDynvahNsAyLFwB3kEHKz1M=
|
go.etcd.io/gofail v0.1.0/go.mod h1:VZBCXYGZhHAinaBiiqYvuDynvahNsAyLFwB3kEHKz1M=
|
||||||
go.opentelemetry.io/collector v0.97.0 h1:qyOju13byHIKEK/JehmTiGMj4pFLa4kDyrOCtTmjHU0=
|
go.opentelemetry.io/collector v0.97.0 h1:qyOju13byHIKEK/JehmTiGMj4pFLa4kDyrOCtTmjHU0=
|
||||||
|
@ -159,11 +159,8 @@ func NewIndexableDocument(key *ResourceKey, rv int64, obj utils.GrafanaMetaAcces
|
|||||||
return doc
|
return doc
|
||||||
}
|
}
|
||||||
|
|
||||||
func StandardDocumentBuilder() DocumentBuilderInfo {
|
func StandardDocumentBuilder() DocumentBuilder {
|
||||||
return DocumentBuilderInfo{
|
return &standardDocumentBuilder{}
|
||||||
Builder: &standardDocumentBuilder{},
|
|
||||||
Fields: StandardSearchFields(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type standardDocumentBuilder struct{}
|
type standardDocumentBuilder struct{}
|
||||||
@ -295,6 +292,30 @@ func StandardSearchFields() SearchableDocumentFields {
|
|||||||
FreeText: true,
|
FreeText: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: SEARCH_FIELD_TAGS,
|
||||||
|
Type: ResourceTableColumnDefinition_STRING,
|
||||||
|
IsArray: true,
|
||||||
|
Description: "Unique tags",
|
||||||
|
Properties: &ResourceTableColumnDefinition_Properties{
|
||||||
|
Filterable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: SEARCH_FIELD_FOLDER,
|
||||||
|
Type: ResourceTableColumnDefinition_STRING,
|
||||||
|
Description: "Kubernetes name for the folder",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: SEARCH_FIELD_RV,
|
||||||
|
Type: ResourceTableColumnDefinition_INT64,
|
||||||
|
Description: "resource version",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: SEARCH_FIELD_CREATED,
|
||||||
|
Type: ResourceTableColumnDefinition_INT64,
|
||||||
|
Description: "created timestamp", // date?
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("failed to initialize standard search fields")
|
panic("failed to initialize standard search fields")
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
func TestStandardDocumentBuilder(t *testing.T) {
|
func TestStandardDocumentBuilder(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
builder := StandardDocumentBuilder().Builder
|
builder := StandardDocumentBuilder()
|
||||||
|
|
||||||
body, err := os.ReadFile("testdata/playlist-resource.json")
|
body, err := os.ReadFile("testdata/playlist-resource.json")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -323,6 +323,7 @@ message WatchEvent {
|
|||||||
Resource previous = 4;
|
Resource previous = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This will soon be deprecated/replaced with ResourceSearchRequest
|
||||||
message SearchRequest {
|
message SearchRequest {
|
||||||
// query string for chosen implementation (currently just bleve)
|
// query string for chosen implementation (currently just bleve)
|
||||||
string query = 1;
|
string query = 1;
|
||||||
@ -342,6 +343,92 @@ message SearchRequest {
|
|||||||
repeated string filters = 10;
|
repeated string filters = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Search within a single resource
|
||||||
|
message ResourceSearchRequest {
|
||||||
|
message Sort {
|
||||||
|
string field = 1;
|
||||||
|
bool desc = 2; // defaults to ascending
|
||||||
|
}
|
||||||
|
|
||||||
|
message Facet {
|
||||||
|
string field = 1;
|
||||||
|
int64 limit = 2;
|
||||||
|
// For now, only term queries, eventually?
|
||||||
|
// numeric queries
|
||||||
|
// date queries
|
||||||
|
}
|
||||||
|
|
||||||
|
// The key must include namespace + group + resource
|
||||||
|
ListOptions options = 1;
|
||||||
|
|
||||||
|
// To search additional resource types, add additional keys to this list
|
||||||
|
// NOTE: queries will only support federation across kinds with common fields
|
||||||
|
repeated ResourceKey federated = 2;
|
||||||
|
|
||||||
|
// When a query exists, it is parsed and used to influence
|
||||||
|
// query string for chosen implementation (currently just bleve)
|
||||||
|
// The score is only relevant when a query exists
|
||||||
|
string query = 3;
|
||||||
|
|
||||||
|
// max results
|
||||||
|
int64 limit = 4;
|
||||||
|
|
||||||
|
// where to start the query (eg, From)
|
||||||
|
int64 offset = 5;
|
||||||
|
|
||||||
|
// sorting
|
||||||
|
repeated Sort sortBy = 6;
|
||||||
|
|
||||||
|
// calculate field statistics
|
||||||
|
map<string,Facet> facet = 7;
|
||||||
|
|
||||||
|
// the return fields (empty will return everything)
|
||||||
|
repeated string fields = 8;
|
||||||
|
|
||||||
|
// explain each result (added to the each row)
|
||||||
|
bool explain = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ResourceSearchResponse {
|
||||||
|
message Facet {
|
||||||
|
string field = 1;
|
||||||
|
// The distinct terms
|
||||||
|
int64 total = 2;
|
||||||
|
// The number of documents that do *not* have this field
|
||||||
|
int64 missing = 3;
|
||||||
|
// Top term stats
|
||||||
|
repeated TermFacet terms = 4;
|
||||||
|
// numeric range
|
||||||
|
// date range facets
|
||||||
|
}
|
||||||
|
|
||||||
|
message TermFacet {
|
||||||
|
string term = 1;
|
||||||
|
int64 count = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error details
|
||||||
|
ErrorResult error = 1;
|
||||||
|
|
||||||
|
// All results exist within this key
|
||||||
|
ResourceKey key = 2;
|
||||||
|
|
||||||
|
// Query results
|
||||||
|
ResourceTable results = 3;
|
||||||
|
|
||||||
|
// The total hit count
|
||||||
|
uint64 total_hits = 4;
|
||||||
|
|
||||||
|
// indicates how expensive was the query with respect to bytes read
|
||||||
|
uint64 query_cost = 5;
|
||||||
|
|
||||||
|
// maximum score across all fields
|
||||||
|
double max_score = 6;
|
||||||
|
|
||||||
|
// Facet results
|
||||||
|
map<string,Facet> facet = 7;
|
||||||
|
}
|
||||||
|
|
||||||
message GroupBy {
|
message GroupBy {
|
||||||
string name = 1;
|
string name = 1;
|
||||||
int64 limit = 2;
|
int64 limit = 2;
|
||||||
@ -352,6 +439,7 @@ message Group {
|
|||||||
int64 count = 2;
|
int64 count = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This will soon be deprecated/replaced with ResourceSearchResponse
|
||||||
message SearchResponse {
|
message SearchResponse {
|
||||||
repeated ResourceWrapper items = 1;
|
repeated ResourceWrapper items = 1;
|
||||||
repeated Group groups = 2;
|
repeated Group groups = 2;
|
||||||
|
@ -12,6 +12,8 @@ import (
|
|||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
|
||||||
|
"github.com/grafana/authlib/authz"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NamespacedResource struct {
|
type NamespacedResource struct {
|
||||||
@ -20,8 +22,52 @@ type NamespacedResource struct {
|
|||||||
Resource string
|
Resource string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All fields are set
|
||||||
|
func (s *NamespacedResource) Valid() bool {
|
||||||
|
return s.Namespace != "" && s.Group != "" && s.Resource != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResourceIndex interface {
|
||||||
|
// Add a document to the index. Note it may not be searchable until after flush is called
|
||||||
|
Write(doc *IndexableDocument) error
|
||||||
|
|
||||||
|
// Mark a resource as deleted. Note it may not be searchable until after flush is called
|
||||||
|
Delete(key *ResourceKey) error
|
||||||
|
|
||||||
|
// Make sure any changes to the index are flushed and available in the next search/origin calls
|
||||||
|
Flush() error
|
||||||
|
|
||||||
|
// Search within a namespaced resource
|
||||||
|
// When working with federated queries, the additional indexes will be passed in explicitly
|
||||||
|
Search(ctx context.Context, access authz.AccessClient, req *ResourceSearchRequest, federate []ResourceIndex) (*ResourceSearchResponse, error)
|
||||||
|
|
||||||
|
// Execute an origin query -- access control is not not checked for each item
|
||||||
|
// NOTE: this will likely be used for provisioning, or it will be removed
|
||||||
|
Origin(ctx context.Context, req *OriginRequest) (*OriginResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchBackend contains the technology specific logic to support search
|
||||||
type SearchBackend interface {
|
type SearchBackend interface {
|
||||||
// TODO
|
// This will return nil if the key does not exist
|
||||||
|
GetIndex(ctx context.Context, key NamespacedResource) (ResourceIndex, error)
|
||||||
|
|
||||||
|
// Build an index from scratch
|
||||||
|
BuildIndex(ctx context.Context,
|
||||||
|
key NamespacedResource,
|
||||||
|
|
||||||
|
// When the size is known, it will be passed along here
|
||||||
|
// Depending on the size, the backend may choose different options (eg: memory vs disk)
|
||||||
|
size int64,
|
||||||
|
|
||||||
|
// The last known resource version (can be used to know that nothing has changed)
|
||||||
|
resourceVersion int64,
|
||||||
|
|
||||||
|
// The non-standard index fields
|
||||||
|
fields SearchableDocumentFields,
|
||||||
|
|
||||||
|
// The builder will write all documents before returning
|
||||||
|
builder func(index ResourceIndex) (int64, error),
|
||||||
|
) (ResourceIndex, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
const tracingPrexfixSearch = "unified_search."
|
const tracingPrexfixSearch = "unified_search."
|
||||||
@ -119,7 +165,7 @@ func (s *searchSupport) init(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *searchSupport) build(ctx context.Context, nsr NamespacedResource, size int64, rv int64) (any, int64, error) {
|
func (s *searchSupport) build(ctx context.Context, nsr NamespacedResource, size int64, rv int64) (ResourceIndex, int64, error) {
|
||||||
_, span := s.tracer.Start(ctx, tracingPrexfixSearch+"Build")
|
_, span := s.tracer.Start(ctx, tracingPrexfixSearch+"Build")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
@ -127,10 +173,57 @@ func (s *searchSupport) build(ctx context.Context, nsr NamespacedResource, size
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
fields := s.builders.GetFields(nsr)
|
||||||
|
|
||||||
s.log.Debug(fmt.Sprintf("TODO, build %+v (size:%d, rv:%d) // builder:%+v\n", nsr, size, rv, builder))
|
s.log.Debug(fmt.Sprintf("TODO, build %+v (size:%d, rv:%d) // builder:%+v\n", nsr, size, rv, builder))
|
||||||
|
|
||||||
return nil, 0, nil
|
key := &ResourceKey{
|
||||||
|
Group: nsr.Group,
|
||||||
|
Resource: nsr.Resource,
|
||||||
|
Namespace: nsr.Namespace,
|
||||||
|
}
|
||||||
|
index, err := s.search.BuildIndex(ctx, nsr, size, rv, fields, func(index ResourceIndex) (int64, error) {
|
||||||
|
rv, err = s.storage.ListIterator(ctx, &ListRequest{
|
||||||
|
Limit: 1000000000000, // big number
|
||||||
|
Options: &ListOptions{
|
||||||
|
Key: key,
|
||||||
|
},
|
||||||
|
}, func(iter ListIterator) error {
|
||||||
|
for iter.Next() {
|
||||||
|
if err = iter.Error(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the key name
|
||||||
|
// Or should we read it from the body?
|
||||||
|
key.Name = iter.Name()
|
||||||
|
|
||||||
|
// Convert it to an indexable document
|
||||||
|
doc, err := builder.BuildDocument(ctx, key, iter.ResourceVersion(), iter.Value())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// And finally write it to the index
|
||||||
|
if err = index.Write(doc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
return rv, err
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
err = index.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// rv is the last RV we read. when watching, we must add all events since that time
|
||||||
|
return index, rv, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type builderCache struct {
|
type builderCache struct {
|
||||||
@ -140,6 +233,9 @@ type builderCache struct {
|
|||||||
// Possible blob support
|
// Possible blob support
|
||||||
blob BlobSupport
|
blob BlobSupport
|
||||||
|
|
||||||
|
// searchable fields initialized once on startup
|
||||||
|
fields map[schema.GroupResource]SearchableDocumentFields
|
||||||
|
|
||||||
// lookup by group, then resource (namespace)
|
// lookup by group, then resource (namespace)
|
||||||
// This is only modified at startup, so we do not need mutex for access
|
// This is only modified at startup, so we do not need mutex for access
|
||||||
lookup map[string]map[string]DocumentBuilderInfo
|
lookup map[string]map[string]DocumentBuilderInfo
|
||||||
@ -151,6 +247,7 @@ type builderCache struct {
|
|||||||
|
|
||||||
func newBuilderCache(cfg []DocumentBuilderInfo, nsCacheSize int, ttl time.Duration) (*builderCache, error) {
|
func newBuilderCache(cfg []DocumentBuilderInfo, nsCacheSize int, ttl time.Duration) (*builderCache, error) {
|
||||||
cache := &builderCache{
|
cache := &builderCache{
|
||||||
|
fields: make(map[schema.GroupResource]SearchableDocumentFields),
|
||||||
lookup: make(map[string]map[string]DocumentBuilderInfo),
|
lookup: make(map[string]map[string]DocumentBuilderInfo),
|
||||||
ns: expirable.NewLRU[NamespacedResource, DocumentBuilder](nsCacheSize, nil, ttl),
|
ns: expirable.NewLRU[NamespacedResource, DocumentBuilder](nsCacheSize, nil, ttl),
|
||||||
}
|
}
|
||||||
@ -173,10 +270,17 @@ func newBuilderCache(cfg []DocumentBuilderInfo, nsCacheSize int, ttl time.Durati
|
|||||||
cache.lookup[b.GroupResource.Group] = g
|
cache.lookup[b.GroupResource.Group] = g
|
||||||
}
|
}
|
||||||
g[b.GroupResource.Resource] = b
|
g[b.GroupResource.Resource] = b
|
||||||
|
|
||||||
|
// Any custom fields
|
||||||
|
cache.fields[b.GroupResource] = b.Fields
|
||||||
}
|
}
|
||||||
return cache, nil
|
return cache, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *builderCache) GetFields(key NamespacedResource) SearchableDocumentFields {
|
||||||
|
return s.fields[schema.GroupResource{Group: key.Group, Resource: key.Resource}]
|
||||||
|
}
|
||||||
|
|
||||||
// context is typically background. Holds an LRU cache for a
|
// context is typically background. Holds an LRU cache for a
|
||||||
func (s *builderCache) get(ctx context.Context, key NamespacedResource) (DocumentBuilder, error) {
|
func (s *builderCache) get(ctx context.Context, key NamespacedResource) (DocumentBuilder, error) {
|
||||||
g, ok := s.lookup[key.Group]
|
g, ok := s.lookup[key.Group]
|
||||||
|
@ -7,13 +7,18 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/data/utils/jsoniter"
|
"github.com/grafana/grafana-plugin-sdk-go/data/utils/jsoniter"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -74,6 +79,10 @@ func (x *ResourceTable) ToK8s() (metav1.Table, error) {
|
|||||||
}
|
}
|
||||||
} else if r.Key != nil {
|
} else if r.Key != nil {
|
||||||
obj := &metav1.PartialObjectMetadata{
|
obj := &metav1.PartialObjectMetadata{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: r.Key.Resource, // :(
|
||||||
|
APIVersion: r.Key.Group, // :(
|
||||||
|
},
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: r.Key.Name,
|
Name: r.Key.Name,
|
||||||
Namespace: r.Key.Namespace,
|
Namespace: r.Key.Namespace,
|
||||||
@ -102,6 +111,8 @@ type TableBuilder struct {
|
|||||||
hasDuplicateNames bool
|
hasDuplicateNames bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ResourceColumnEncoder = func(v any) ([]byte, error)
|
||||||
|
|
||||||
func NewTableBuilder(cols []*ResourceTableColumnDefinition) (*TableBuilder, error) {
|
func NewTableBuilder(cols []*ResourceTableColumnDefinition) (*TableBuilder, error) {
|
||||||
table := &TableBuilder{
|
table := &TableBuilder{
|
||||||
ResourceTable: ResourceTable{
|
ResourceTable: ResourceTable{
|
||||||
@ -124,6 +135,15 @@ func NewTableBuilder(cols []*ResourceTableColumnDefinition) (*TableBuilder, erro
|
|||||||
return table, err
|
return table, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *TableBuilder) Encoders() []ResourceColumnEncoder {
|
||||||
|
encoders := make([]ResourceColumnEncoder, len(x.Columns))
|
||||||
|
for i, f := range x.Columns {
|
||||||
|
v := x.lookup[f.Name]
|
||||||
|
encoders[i] = v.Encode
|
||||||
|
}
|
||||||
|
return encoders
|
||||||
|
}
|
||||||
|
|
||||||
func (x *TableBuilder) AddRow(key *ResourceKey, rv int64, vals map[string]any) error {
|
func (x *TableBuilder) AddRow(key *ResourceKey, rv int64, vals map[string]any) error {
|
||||||
row := &ResourceTableRow{
|
row := &ResourceTableRow{
|
||||||
Key: key,
|
Key: key,
|
||||||
@ -395,6 +415,8 @@ func (x *resourceTableColumn) Encode(v any) ([]byte, error) {
|
|||||||
f = int64(typed)
|
f = int64(typed)
|
||||||
case float32:
|
case float32:
|
||||||
f = int64(typed)
|
f = int64(typed)
|
||||||
|
case float64:
|
||||||
|
f = int64(typed)
|
||||||
case uint64:
|
case uint64:
|
||||||
f = int64(typed)
|
f = int64(typed)
|
||||||
case uint:
|
case uint:
|
||||||
@ -547,3 +569,29 @@ func (x *resourceTableColumn) Decode(buff []byte) (any, error) {
|
|||||||
}
|
}
|
||||||
return v, err
|
return v, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AssertTableSnapshot will match a ResourceTable vs the saved value
|
||||||
|
func AssertTableSnapshot(t *testing.T, path string, table *ResourceTable) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
k8sTable, err := table.ToK8s()
|
||||||
|
require.NoError(t, err, "unable to create table response", path)
|
||||||
|
actual, err := json.MarshalIndent(k8sTable, "", " ")
|
||||||
|
require.NoError(t, err, "unable to write table json", path)
|
||||||
|
|
||||||
|
// Safe to disable, this is a test.
|
||||||
|
// nolint:gosec
|
||||||
|
expected, err := os.ReadFile(path)
|
||||||
|
if err != nil || len(expected) < 1 {
|
||||||
|
assert.Fail(t, "missing file: %s", path)
|
||||||
|
} else if assert.JSONEq(t, string(expected), string(actual)) {
|
||||||
|
return // everything is OK
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the snapshot
|
||||||
|
// Safe to disable, this is a test.
|
||||||
|
// nolint:gosec
|
||||||
|
err = os.WriteFile(path, actual, 0600)
|
||||||
|
require.NoError(t, err)
|
||||||
|
fmt.Printf("Updated table snapshot: %s\n", path)
|
||||||
|
}
|
||||||
|
@ -1,44 +1,15 @@
|
|||||||
package resource
|
package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AssertTableSnapshot will match a ResourceTable vs the saved value
|
|
||||||
func AssertTableSnapshot(t *testing.T, path string, table *ResourceTable) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
k8sTable, err := table.ToK8s()
|
|
||||||
require.NoError(t, err, "unable to create table response", path)
|
|
||||||
actual, err := json.MarshalIndent(k8sTable, "", " ")
|
|
||||||
require.NoError(t, err, "unable to write table json", path)
|
|
||||||
|
|
||||||
// Safe to disable, this is a test.
|
|
||||||
// nolint:gosec
|
|
||||||
expected, err := os.ReadFile(path)
|
|
||||||
if err != nil || len(expected) < 1 {
|
|
||||||
assert.Fail(t, "missing file")
|
|
||||||
} else if assert.JSONEq(t, string(expected), string(actual)) {
|
|
||||||
return // everything is OK
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the snapshot
|
|
||||||
// Safe to disable, this is a test.
|
|
||||||
// nolint:gosec
|
|
||||||
err = os.WriteFile(path, actual, 0600)
|
|
||||||
require.NoError(t, err)
|
|
||||||
fmt.Printf("Updated table snapshot: %s\n", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTableFormat(t *testing.T) {
|
func TestTableFormat(t *testing.T) {
|
||||||
columns := []*ResourceTableColumnDefinition{
|
columns := []*ResourceTableColumnDefinition{
|
||||||
{
|
{
|
||||||
|
@ -41,6 +41,8 @@
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
"object": {
|
"object": {
|
||||||
|
"kind": "xyz",
|
||||||
|
"apiVersion": "ggg",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"name": "aaa",
|
"name": "aaa",
|
||||||
"namespace": "default",
|
"namespace": "default",
|
||||||
@ -60,6 +62,8 @@
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
"object": {
|
"object": {
|
||||||
|
"kind": "xyz",
|
||||||
|
"apiVersion": "ggg",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"name": "bbb",
|
"name": "bbb",
|
||||||
"namespace": "default",
|
"namespace": "default",
|
||||||
|
547
pkg/storage/unified/search/bleve.go
Normal file
547
pkg/storage/unified/search/bleve.go
Normal file
@ -0,0 +1,547 @@
|
|||||||
|
package search
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/blevesearch/bleve/v2"
|
||||||
|
"github.com/blevesearch/bleve/v2/search"
|
||||||
|
"github.com/blevesearch/bleve/v2/search/query"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
"k8s.io/apimachinery/pkg/selection"
|
||||||
|
|
||||||
|
"github.com/grafana/authlib/authz"
|
||||||
|
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
const tracingPrexfixBleve = "unified_search.bleve."
|
||||||
|
|
||||||
|
var _ resource.SearchBackend = &bleveBackend{}
|
||||||
|
var _ resource.ResourceIndex = &bleveIndex{}
|
||||||
|
|
||||||
|
type bleveOptions struct {
|
||||||
|
// The root folder where file objects are saved
|
||||||
|
Root string
|
||||||
|
|
||||||
|
// The resource count where values switch from memory to file based
|
||||||
|
FileThreshold int64
|
||||||
|
|
||||||
|
// How big should a batch get before flushing
|
||||||
|
// ?? not totally sure the units
|
||||||
|
BatchSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
type bleveBackend struct {
|
||||||
|
tracer trace.Tracer
|
||||||
|
log *slog.Logger
|
||||||
|
opts bleveOptions
|
||||||
|
|
||||||
|
// cache info
|
||||||
|
cache map[resource.NamespacedResource]*bleveIndex
|
||||||
|
cacheMu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBleveBackend(opts bleveOptions, tracer trace.Tracer, reg prometheus.Registerer) *bleveBackend {
|
||||||
|
b := &bleveBackend{
|
||||||
|
log: slog.Default().With("logger", "bleve-backend"),
|
||||||
|
tracer: tracer,
|
||||||
|
cache: make(map[resource.NamespacedResource]*bleveIndex),
|
||||||
|
opts: opts,
|
||||||
|
}
|
||||||
|
|
||||||
|
if reg != nil {
|
||||||
|
b.log.Info("TODO, register metrics collectors!")
|
||||||
|
}
|
||||||
|
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will return nil if the key does not exist
|
||||||
|
func (b *bleveBackend) GetIndex(ctx context.Context, key resource.NamespacedResource) (resource.ResourceIndex, error) {
|
||||||
|
b.cacheMu.RLock()
|
||||||
|
defer b.cacheMu.RUnlock()
|
||||||
|
|
||||||
|
idx, ok := b.cache[key]
|
||||||
|
if ok {
|
||||||
|
return idx, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build an index from scratch
|
||||||
|
func (b *bleveBackend) BuildIndex(ctx context.Context,
|
||||||
|
key resource.NamespacedResource,
|
||||||
|
|
||||||
|
// When the size is known, it will be passed along here
|
||||||
|
// Depending on the size, the backend may choose different options (eg: memory vs disk)
|
||||||
|
size int64,
|
||||||
|
|
||||||
|
// The last known resource version can be used to know that we can skip calling the builder
|
||||||
|
resourceVersion int64,
|
||||||
|
|
||||||
|
// the non-standard searchable fields
|
||||||
|
fields resource.SearchableDocumentFields,
|
||||||
|
|
||||||
|
// The builder will write all documents before returning
|
||||||
|
builder func(index resource.ResourceIndex) (int64, error),
|
||||||
|
) (resource.ResourceIndex, error) {
|
||||||
|
b.cacheMu.Lock()
|
||||||
|
defer b.cacheMu.Unlock()
|
||||||
|
|
||||||
|
_, span := b.tracer.Start(ctx, tracingPrexfixBleve+"BuildIndex")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var index bleve.Index
|
||||||
|
|
||||||
|
mapper := getBleveMappings(fields)
|
||||||
|
|
||||||
|
if size > b.opts.FileThreshold {
|
||||||
|
dir := filepath.Join(b.opts.Root, key.Namespace, fmt.Sprintf("%s.%s", key.Resource, key.Group))
|
||||||
|
index, err = bleve.New(dir, mapper)
|
||||||
|
if err == nil {
|
||||||
|
b.log.Info("TODO, check last RV so we can see if the numbers have changed", "dir", dir)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
index, err = bleve.NewMemOnly(mapper)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batch all the changes
|
||||||
|
idx := &bleveIndex{
|
||||||
|
key: key,
|
||||||
|
index: index,
|
||||||
|
batch: index.NewBatch(),
|
||||||
|
batchSize: b.opts.BatchSize,
|
||||||
|
fields: fields,
|
||||||
|
standard: resource.StandardSearchFields(),
|
||||||
|
}
|
||||||
|
|
||||||
|
idx.allFields, err = getAllFields(idx.standard, fields)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = builder(idx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush the batch
|
||||||
|
err = idx.Flush()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b.cache[key] = idx
|
||||||
|
return idx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type bleveIndex struct {
|
||||||
|
key resource.NamespacedResource
|
||||||
|
index bleve.Index
|
||||||
|
|
||||||
|
standard resource.SearchableDocumentFields
|
||||||
|
fields resource.SearchableDocumentFields
|
||||||
|
|
||||||
|
// The values returned with all
|
||||||
|
allFields []*resource.ResourceTableColumnDefinition
|
||||||
|
|
||||||
|
// only valid in single thread
|
||||||
|
batch *bleve.Batch
|
||||||
|
batchSize int // ??? not totally sure the units here
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements resource.DocumentIndex.
|
||||||
|
func (b *bleveIndex) Write(v *resource.IndexableDocument) error {
|
||||||
|
// remove references (for now!)
|
||||||
|
v.References = nil
|
||||||
|
if b.batch != nil {
|
||||||
|
err := b.batch.Index(v.Key.SearchID(), v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if b.batch.Size() > b.batchSize {
|
||||||
|
err = b.index.Batch(b.batch)
|
||||||
|
b.batch.Reset() // clear the batch
|
||||||
|
}
|
||||||
|
return err // nil
|
||||||
|
}
|
||||||
|
return b.index.Index(v.Key.SearchID(), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete implements resource.DocumentIndex.
|
||||||
|
func (b *bleveIndex) Delete(key *resource.ResourceKey) error {
|
||||||
|
if b.batch != nil {
|
||||||
|
return fmt.Errorf("unexpected delete while building batch")
|
||||||
|
}
|
||||||
|
return b.index.Delete(key.SearchID())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implements resource.DocumentIndex.
|
||||||
|
func (b *bleveIndex) Flush() (err error) {
|
||||||
|
if b.batch != nil {
|
||||||
|
err = b.index.Batch(b.batch)
|
||||||
|
b.batch.Reset()
|
||||||
|
b.batch = nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Origin implements resource.DocumentIndex.
|
||||||
|
func (b *bleveIndex) Origin(ctx context.Context, req *resource.OriginRequest) (*resource.OriginResponse, error) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search implements resource.DocumentIndex.
|
||||||
|
func (b *bleveIndex) Search(
|
||||||
|
ctx context.Context,
|
||||||
|
access authz.AccessClient,
|
||||||
|
req *resource.ResourceSearchRequest,
|
||||||
|
federate []resource.ResourceIndex, // For federated queries, these will match the values in req.federate
|
||||||
|
) (*resource.ResourceSearchResponse, error) {
|
||||||
|
if req.Options == nil || req.Options.Key == nil {
|
||||||
|
return &resource.ResourceSearchResponse{
|
||||||
|
Error: resource.NewBadRequestError("missing query key"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &resource.ResourceSearchResponse{
|
||||||
|
Error: b.verifyKey(req.Options.Key),
|
||||||
|
}
|
||||||
|
if response.Error != nil {
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifies the index federation
|
||||||
|
index, err := b.getIndex(req, federate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert protobuf request to bleve request
|
||||||
|
searchrequest, e := toBleveSearchRequest(req, access)
|
||||||
|
if e != nil {
|
||||||
|
response.Error = e
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show all fields when nothing is selected
|
||||||
|
if len(searchrequest.Fields) < 1 && req.Limit > 0 {
|
||||||
|
f, err := b.index.Fields()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
searchrequest.Fields = f
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := index.Search(searchrequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response.TotalHits = res.Total
|
||||||
|
response.QueryCost = res.Cost
|
||||||
|
response.MaxScore = res.MaxScore
|
||||||
|
|
||||||
|
response.Results, err = b.hitsToTable(searchrequest.Fields, res.Hits, req.Explain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write frame as JSON
|
||||||
|
//response.Frame, err = frame.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the facet fields
|
||||||
|
for k, v := range res.Facets {
|
||||||
|
f := &resource.ResourceSearchResponse_Facet{
|
||||||
|
Field: v.Field,
|
||||||
|
Total: int64(v.Total),
|
||||||
|
Missing: int64(v.Missing),
|
||||||
|
}
|
||||||
|
if v.Terms != nil {
|
||||||
|
for _, t := range v.Terms.Terms() {
|
||||||
|
f.Terms = append(f.Terms, &resource.ResourceSearchResponse_TermFacet{
|
||||||
|
Term: t.Term,
|
||||||
|
Count: int64(t.Count),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if response.Facet == nil {
|
||||||
|
response.Facet = make(map[string]*resource.ResourceSearchResponse_Facet)
|
||||||
|
}
|
||||||
|
response.Facet[k] = f
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the request key matches the index
|
||||||
|
func (b *bleveIndex) verifyKey(key *resource.ResourceKey) *resource.ErrorResult {
|
||||||
|
if key.Namespace != b.key.Namespace {
|
||||||
|
return resource.NewBadRequestError("namespace mismatch (expected " + b.key.Namespace + ")")
|
||||||
|
}
|
||||||
|
if key.Group != b.key.Group {
|
||||||
|
return resource.NewBadRequestError("group mismatch (expected " + b.key.Group + ")")
|
||||||
|
}
|
||||||
|
if key.Resource != b.key.Resource {
|
||||||
|
return resource.NewBadRequestError("resource mismatch (expected " + b.key.Resource + ")")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bleveIndex) getIndex(
|
||||||
|
req *resource.ResourceSearchRequest,
|
||||||
|
federate []resource.ResourceIndex,
|
||||||
|
) (bleve.Index, error) {
|
||||||
|
if len(req.Federated) != len(federate) {
|
||||||
|
return nil, fmt.Errorf("federation is misconfigured")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search across resources using
|
||||||
|
// https://blevesearch.com/docs/IndexAlias/
|
||||||
|
if len(federate) > 0 {
|
||||||
|
all := []bleve.Index{b.index}
|
||||||
|
for i, extra := range federate {
|
||||||
|
typedindex, ok := extra.(*bleveIndex)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("federated indexes must be the same type")
|
||||||
|
}
|
||||||
|
if typedindex.verifyKey(req.Federated[i]) != nil {
|
||||||
|
return nil, fmt.Errorf("federated index keys do not match")
|
||||||
|
}
|
||||||
|
all = append(all, typedindex.index)
|
||||||
|
}
|
||||||
|
return bleve.NewIndexAlias(all...), nil
|
||||||
|
}
|
||||||
|
return b.index, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toBleveSearchRequest(req *resource.ResourceSearchRequest, access authz.AccessClient) (*bleve.SearchRequest, *resource.ErrorResult) {
|
||||||
|
searchrequest := &bleve.SearchRequest{
|
||||||
|
Fields: req.Fields,
|
||||||
|
Size: int(req.Limit),
|
||||||
|
From: int(req.Offset),
|
||||||
|
Explain: req.Explain,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Currently everything is within an AND query
|
||||||
|
queries := []query.Query{}
|
||||||
|
if len(req.Options.Labels) > 0 {
|
||||||
|
for _, v := range req.Options.Labels {
|
||||||
|
q, err := requirementQuery(v, "labels.")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
queries = append(queries, q)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(req.Options.Fields) > 0 {
|
||||||
|
for _, v := range req.Options.Fields {
|
||||||
|
q, err := requirementQuery(v, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
queries = append(queries, q)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Query != "" {
|
||||||
|
// ??? Should expose the full power of query parsing here?
|
||||||
|
// it is great for exploration, but also hard to change in the future
|
||||||
|
q := bleve.NewQueryStringQuery(req.Query)
|
||||||
|
queries = append(queries, q)
|
||||||
|
}
|
||||||
|
|
||||||
|
if access != nil {
|
||||||
|
// TODO AUTHZ!!!!
|
||||||
|
// Need to add an authz filter into the mix
|
||||||
|
// See: https://github.com/grafana/grafana/blob/v11.3.0/pkg/services/searchV2/bluge.go
|
||||||
|
// NOTE, we likely want to pass in the already called checker because the resource server
|
||||||
|
// will first need to check if we can see anything (or everything!) for this resource
|
||||||
|
fmt.Printf("TODO... check authorization")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(queries) {
|
||||||
|
case 0:
|
||||||
|
searchrequest.Query = bleve.NewMatchAllQuery()
|
||||||
|
case 1:
|
||||||
|
searchrequest.Query = queries[0]
|
||||||
|
default:
|
||||||
|
searchrequest.Query = bleve.NewConjunctionQuery(queries...) // AND
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range req.Facet {
|
||||||
|
if searchrequest.Facets == nil {
|
||||||
|
searchrequest.Facets = make(bleve.FacetsRequest)
|
||||||
|
}
|
||||||
|
searchrequest.Facets[k] = bleve.NewFacetRequest(v.Field, int(v.Limit))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the sort fields
|
||||||
|
for _, sort := range req.SortBy {
|
||||||
|
// hardcoded (for now)
|
||||||
|
if strings.HasPrefix(sort.Field, "stats.") {
|
||||||
|
searchrequest.Sort = append(searchrequest.Sort, &search.SortField{
|
||||||
|
Field: sort.Field,
|
||||||
|
Desc: sort.Desc,
|
||||||
|
Type: search.SortFieldAsNumber, // force for now!
|
||||||
|
Mode: search.SortFieldDefault, // ???
|
||||||
|
Missing: search.SortFieldMissingLast,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default support
|
||||||
|
input := sort.Field
|
||||||
|
if sort.Desc {
|
||||||
|
input = "-" + sort.Field
|
||||||
|
}
|
||||||
|
s := search.ParseSearchSortString(input)
|
||||||
|
searchrequest.Sort = append(searchrequest.Sort, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always sort by *something*, otherwise the order is unstable
|
||||||
|
if len(searchrequest.Sort) == 0 {
|
||||||
|
searchrequest.Sort = append(searchrequest.Sort, &search.SortDocID{
|
||||||
|
Desc: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return searchrequest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert a "requirement" into a bleve query
|
||||||
|
func requirementQuery(req *resource.Requirement, prefix string) (query.Query, *resource.ErrorResult) {
|
||||||
|
switch selection.Operator(req.Operator) {
|
||||||
|
case selection.Equals, selection.DoubleEquals:
|
||||||
|
if len(req.Values) != 1 {
|
||||||
|
return nil, resource.NewBadRequestError("equals query can have one value")
|
||||||
|
}
|
||||||
|
q := query.NewMatchQuery(req.Values[0])
|
||||||
|
q.FieldVal = prefix + req.Key
|
||||||
|
return q, nil
|
||||||
|
|
||||||
|
case selection.NotEquals:
|
||||||
|
case selection.DoesNotExist:
|
||||||
|
case selection.GreaterThan:
|
||||||
|
case selection.LessThan:
|
||||||
|
case selection.Exists:
|
||||||
|
case selection.In:
|
||||||
|
case selection.NotIn:
|
||||||
|
}
|
||||||
|
return nil, resource.NewBadRequestError(
|
||||||
|
fmt.Sprintf("unsupported query operation (%s %s %v)", req.Key, req.Operator, req.Values),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bleveIndex) hitsToTable(selectFields []string, hits search.DocumentMatchCollection, explain bool) (*resource.ResourceTable, error) {
|
||||||
|
fields := []*resource.ResourceTableColumnDefinition{}
|
||||||
|
for _, name := range selectFields {
|
||||||
|
if name == "_all" {
|
||||||
|
fields = b.allFields
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
f := b.standard.Field(name)
|
||||||
|
if f == nil && b.fields != nil {
|
||||||
|
f = b.fields.Field(name)
|
||||||
|
}
|
||||||
|
if f == nil {
|
||||||
|
// Labels as a string
|
||||||
|
if strings.HasPrefix(name, "labels.") {
|
||||||
|
f = &resource.ResourceTableColumnDefinition{
|
||||||
|
Name: name,
|
||||||
|
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return nil, fmt.Errorf("unknown response field: " + name)
|
||||||
|
if f == nil {
|
||||||
|
continue // OK for now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fields = append(fields, f)
|
||||||
|
}
|
||||||
|
if explain {
|
||||||
|
fields = append(fields, b.standard.Field(resource.SEARCH_FIELD_EXPLAIN))
|
||||||
|
}
|
||||||
|
|
||||||
|
builder, err := resource.NewTableBuilder(fields)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
encoders := builder.Encoders()
|
||||||
|
|
||||||
|
table := &resource.ResourceTable{
|
||||||
|
Columns: fields,
|
||||||
|
Rows: make([]*resource.ResourceTableRow, hits.Len()),
|
||||||
|
}
|
||||||
|
for rowID, match := range hits {
|
||||||
|
row := &resource.ResourceTableRow{
|
||||||
|
Key: &resource.ResourceKey{},
|
||||||
|
Cells: make([][]byte, len(fields)),
|
||||||
|
}
|
||||||
|
table.Rows[rowID] = row
|
||||||
|
|
||||||
|
err := row.Key.ReadSearchID(match.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, f := range fields {
|
||||||
|
if f.Name == resource.SEARCH_FIELD_ID {
|
||||||
|
row.Cells[i] = []byte(match.ID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// QUICK QUICK... more options yes
|
||||||
|
v := match.Fields[f.Name]
|
||||||
|
if v != nil {
|
||||||
|
// Encode the value to protobuf
|
||||||
|
row.Cells[i], err = encoders[i](v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error encoding (row:%d/col:%d) %v %w", rowID, i, v, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return table, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAllFields(standard resource.SearchableDocumentFields, custom resource.SearchableDocumentFields) ([]*resource.ResourceTableColumnDefinition, error) {
|
||||||
|
fields := []*resource.ResourceTableColumnDefinition{
|
||||||
|
standard.Field(resource.SEARCH_FIELD_ID),
|
||||||
|
standard.Field(resource.SEARCH_FIELD_TITLE),
|
||||||
|
standard.Field(resource.SEARCH_FIELD_TAGS),
|
||||||
|
standard.Field(resource.SEARCH_FIELD_FOLDER),
|
||||||
|
standard.Field(resource.SEARCH_FIELD_RV),
|
||||||
|
standard.Field(resource.SEARCH_FIELD_CREATED),
|
||||||
|
}
|
||||||
|
|
||||||
|
if custom != nil {
|
||||||
|
for _, name := range custom.Fields() {
|
||||||
|
f := custom.Field(name)
|
||||||
|
if f.Priority > 10 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fields = append(fields, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, field := range fields {
|
||||||
|
if field == nil {
|
||||||
|
return nil, fmt.Errorf("invalid all field")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fields, nil
|
||||||
|
}
|
68
pkg/storage/unified/search/bleve_mappings.go
Normal file
68
pkg/storage/unified/search/bleve_mappings.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package search
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/blevesearch/bleve/v2"
|
||||||
|
"github.com/blevesearch/bleve/v2/analysis/analyzer/keyword"
|
||||||
|
"github.com/blevesearch/bleve/v2/mapping"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getBleveMappings(fields resource.SearchableDocumentFields) mapping.IndexMapping {
|
||||||
|
mapper := bleve.NewIndexMapping()
|
||||||
|
mapper.DefaultMapping = getBleveDocMappings(fields)
|
||||||
|
return mapper
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBleveDocMappings(_ resource.SearchableDocumentFields) *mapping.DocumentMapping {
|
||||||
|
mapper := bleve.NewDocumentStaticMapping()
|
||||||
|
mapper.AddFieldMapping(&mapping.FieldMapping{
|
||||||
|
Name: "title",
|
||||||
|
Type: "text",
|
||||||
|
// TODO - if we don't want title to be a keyword, we can use this
|
||||||
|
// set the title field to use keyword analyzer so it sorts by the whole phrase
|
||||||
|
// https://github.com/blevesearch/bleve/issues/417#issuecomment-245273022
|
||||||
|
Analyzer: keyword.Name,
|
||||||
|
Store: true,
|
||||||
|
Index: true,
|
||||||
|
IncludeTermVectors: true,
|
||||||
|
IncludeInAll: true,
|
||||||
|
DocValues: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
mapper.AddFieldMapping(&mapping.FieldMapping{
|
||||||
|
Name: "description",
|
||||||
|
Type: "text",
|
||||||
|
Store: true,
|
||||||
|
Index: true,
|
||||||
|
IncludeTermVectors: false,
|
||||||
|
IncludeInAll: false,
|
||||||
|
DocValues: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
mapper.AddFieldMapping(&mapping.FieldMapping{
|
||||||
|
Name: "tags",
|
||||||
|
Type: "text",
|
||||||
|
Analyzer: keyword.Name,
|
||||||
|
Store: true,
|
||||||
|
Index: true,
|
||||||
|
IncludeTermVectors: false,
|
||||||
|
IncludeInAll: false,
|
||||||
|
DocValues: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
mapper.AddFieldMapping(&mapping.FieldMapping{
|
||||||
|
Name: "folder",
|
||||||
|
Type: "text",
|
||||||
|
Analyzer: keyword.Name,
|
||||||
|
Store: true,
|
||||||
|
Index: true,
|
||||||
|
IncludeTermVectors: false,
|
||||||
|
IncludeInAll: false,
|
||||||
|
DocValues: true, // will be needed for authz client
|
||||||
|
})
|
||||||
|
|
||||||
|
mapper.Dynamic = true
|
||||||
|
|
||||||
|
return mapper
|
||||||
|
}
|
46
pkg/storage/unified/search/bleve_mappings_test.go
Normal file
46
pkg/storage/unified/search/bleve_mappings_test.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package search
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/blevesearch/bleve/v2/document"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
|
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDocumentMapping(t *testing.T) {
|
||||||
|
mappings := getBleveMappings(nil)
|
||||||
|
data := resource.IndexableDocument{
|
||||||
|
Title: "title",
|
||||||
|
Description: "descr",
|
||||||
|
Tags: []string{"a", "b"},
|
||||||
|
Created: 12345,
|
||||||
|
Folder: "xyz",
|
||||||
|
CreatedBy: "user:ryan",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"a": "b",
|
||||||
|
"x": "y",
|
||||||
|
},
|
||||||
|
RV: 1234,
|
||||||
|
RepoInfo: &utils.ResourceRepositoryInfo{
|
||||||
|
Name: "nnn",
|
||||||
|
Path: "ppp",
|
||||||
|
Hash: "hhh",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
doc := document.NewDocument("id")
|
||||||
|
err := mappings.MapDocument(doc, data)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for _, f := range doc.Fields {
|
||||||
|
fmt.Printf("%s = %+v\n", f.Name(), f.Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("DOC: fields %d\n", len(doc.Fields))
|
||||||
|
fmt.Printf("DOC: size %d\n", doc.Size())
|
||||||
|
require.Equal(t, 15, len(doc.Fields))
|
||||||
|
}
|
289
pkg/storage/unified/search/bleve_test.go
Normal file
289
pkg/storage/unified/search/bleve_test.go
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
package search
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||||
|
"github.com/grafana/grafana/pkg/services/store/kind/dashboard"
|
||||||
|
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBleveBackend(t *testing.T) {
|
||||||
|
dashboardskey := &resource.ResourceKey{
|
||||||
|
Namespace: "default",
|
||||||
|
Group: "dashboard.grafana.app",
|
||||||
|
Resource: "dashboards",
|
||||||
|
}
|
||||||
|
folderKey := &resource.ResourceKey{
|
||||||
|
Namespace: dashboardskey.Namespace,
|
||||||
|
Group: "folder.grafana.app",
|
||||||
|
Resource: "folders",
|
||||||
|
}
|
||||||
|
tmpdir, err := os.CreateTemp("", "bleve-test")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
backend := NewBleveBackend(
|
||||||
|
bleveOptions{
|
||||||
|
Root: tmpdir.Name(),
|
||||||
|
FileThreshold: 5, // with more than 5 items we create a file on disk
|
||||||
|
},
|
||||||
|
tracing.NewNoopTracerService(),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
rv := int64(10)
|
||||||
|
ctx := context.Background()
|
||||||
|
var dashboardsIndex resource.ResourceIndex
|
||||||
|
var foldersIndex resource.ResourceIndex
|
||||||
|
|
||||||
|
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{
|
||||||
|
Namespace: namespace,
|
||||||
|
Blob: blob,
|
||||||
|
Stats: NewDashboardStatsLookup(nil), // empty stats
|
||||||
|
DatasourceLookup: dashboard.CreateDatasourceLookup([]*dashboard.DatasourceQueryResult{{}}),
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
index, err := backend.BuildIndex(ctx, resource.NamespacedResource{
|
||||||
|
Namespace: key.Namespace,
|
||||||
|
Group: key.Group,
|
||||||
|
Resource: key.Resource,
|
||||||
|
}, 2, rv, info.Fields, func(index resource.ResourceIndex) (int64, error) {
|
||||||
|
_ = index.Write(&resource.IndexableDocument{
|
||||||
|
RV: 1,
|
||||||
|
Key: &resource.ResourceKey{
|
||||||
|
Name: "aaa",
|
||||||
|
Namespace: "ns",
|
||||||
|
Group: "g",
|
||||||
|
Resource: "dash",
|
||||||
|
},
|
||||||
|
Title: "bbb (dash)",
|
||||||
|
Folder: "xxx",
|
||||||
|
Fields: map[string]any{
|
||||||
|
DASHBOARD_LEGACY_ID: 12,
|
||||||
|
DASHBOARD_PANEL_TYPES: []string{"timeseries", "table"},
|
||||||
|
DASHBOARD_ERRORS_TODAY: 25,
|
||||||
|
},
|
||||||
|
Tags: []string{"aa", "bb"},
|
||||||
|
})
|
||||||
|
_ = index.Write(&resource.IndexableDocument{
|
||||||
|
RV: 2,
|
||||||
|
Key: &resource.ResourceKey{
|
||||||
|
Name: "bbb",
|
||||||
|
Namespace: "ns",
|
||||||
|
Group: "g",
|
||||||
|
Resource: "dash",
|
||||||
|
},
|
||||||
|
Title: "aaa (dash)",
|
||||||
|
Folder: "xxx",
|
||||||
|
Fields: map[string]any{
|
||||||
|
DASHBOARD_LEGACY_ID: 12,
|
||||||
|
DASHBOARD_PANEL_TYPES: []string{"timeseries"},
|
||||||
|
DASHBOARD_ERRORS_TODAY: 40,
|
||||||
|
},
|
||||||
|
Tags: []string{"aa"},
|
||||||
|
Labels: map[string]string{
|
||||||
|
"region": "east",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
_ = index.Write(&resource.IndexableDocument{
|
||||||
|
RV: 3,
|
||||||
|
Key: &resource.ResourceKey{
|
||||||
|
Name: "ccc",
|
||||||
|
Namespace: "ns",
|
||||||
|
Group: "g",
|
||||||
|
Resource: "dash",
|
||||||
|
},
|
||||||
|
Title: "ccc (dash)",
|
||||||
|
Folder: "xxx",
|
||||||
|
Fields: map[string]any{
|
||||||
|
DASHBOARD_LEGACY_ID: 12,
|
||||||
|
},
|
||||||
|
Tags: []string{"aa"},
|
||||||
|
Labels: map[string]string{
|
||||||
|
"region": "west",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return rv, nil
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, index)
|
||||||
|
dashboardsIndex = index
|
||||||
|
|
||||||
|
rsp, err := index.Search(ctx, nil, &resource.ResourceSearchRequest{
|
||||||
|
Options: &resource.ListOptions{
|
||||||
|
Key: key,
|
||||||
|
},
|
||||||
|
Limit: 100000,
|
||||||
|
SortBy: []*resource.ResourceSearchRequest_Sort{
|
||||||
|
{Field: "title", Desc: true}, // ccc,bbb,aaa
|
||||||
|
},
|
||||||
|
Facet: map[string]*resource.ResourceSearchRequest_Facet{
|
||||||
|
"tags": {
|
||||||
|
Field: "tags",
|
||||||
|
Limit: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Nil(t, rsp.Error)
|
||||||
|
require.NotNil(t, rsp.Results)
|
||||||
|
require.NotNil(t, rsp.Facet)
|
||||||
|
|
||||||
|
// Match the results
|
||||||
|
resource.AssertTableSnapshot(t, filepath.Join("testdata", "manual-dashboard.json"), rsp.Results)
|
||||||
|
|
||||||
|
// Get the tags facets
|
||||||
|
facet, ok := rsp.Facet["tags"]
|
||||||
|
require.True(t, ok)
|
||||||
|
disp, err := json.MarshalIndent(facet, "", " ")
|
||||||
|
require.NoError(t, err)
|
||||||
|
//fmt.Printf("%s\n", disp)
|
||||||
|
require.JSONEq(t, `{
|
||||||
|
"field": "tags",
|
||||||
|
"total": 4,
|
||||||
|
"terms": [
|
||||||
|
{
|
||||||
|
"term": "aa",
|
||||||
|
"count": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "bb",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`, string(disp))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("build folders", func(t *testing.T) {
|
||||||
|
key := folderKey
|
||||||
|
var fields resource.SearchableDocumentFields
|
||||||
|
|
||||||
|
index, err := backend.BuildIndex(ctx, resource.NamespacedResource{
|
||||||
|
Namespace: key.Namespace,
|
||||||
|
Group: key.Group,
|
||||||
|
Resource: key.Resource,
|
||||||
|
}, 2, rv, fields, func(index resource.ResourceIndex) (int64, error) {
|
||||||
|
_ = index.Write(&resource.IndexableDocument{
|
||||||
|
RV: 1,
|
||||||
|
Key: &resource.ResourceKey{
|
||||||
|
Name: "zzz",
|
||||||
|
Namespace: "ns",
|
||||||
|
Group: "g",
|
||||||
|
Resource: "folder",
|
||||||
|
},
|
||||||
|
Title: "zzz (folder)",
|
||||||
|
})
|
||||||
|
_ = index.Write(&resource.IndexableDocument{
|
||||||
|
RV: 2,
|
||||||
|
Key: &resource.ResourceKey{
|
||||||
|
Name: "yyy",
|
||||||
|
Namespace: "ns",
|
||||||
|
Group: "g",
|
||||||
|
Resource: "folder",
|
||||||
|
},
|
||||||
|
Title: "yyy (folder)",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"region": "west",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return rv, nil
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, index)
|
||||||
|
foldersIndex = index
|
||||||
|
|
||||||
|
rsp, err := index.Search(ctx, nil, &resource.ResourceSearchRequest{
|
||||||
|
Options: &resource.ListOptions{
|
||||||
|
Key: key,
|
||||||
|
},
|
||||||
|
Limit: 100000,
|
||||||
|
}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Nil(t, rsp.Error)
|
||||||
|
require.NotNil(t, rsp.Results)
|
||||||
|
require.Nil(t, rsp.Facet)
|
||||||
|
|
||||||
|
resource.AssertTableSnapshot(t, filepath.Join("testdata", "manual-folder.json"), rsp.Results)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("simple federation", func(t *testing.T) {
|
||||||
|
// The other tests must run first to build the indexes
|
||||||
|
require.NotNil(t, dashboardsIndex)
|
||||||
|
require.NotNil(t, foldersIndex)
|
||||||
|
|
||||||
|
// Use a federated query to get both results together, sorted by title
|
||||||
|
rsp, err := dashboardsIndex.Search(ctx, nil, &resource.ResourceSearchRequest{
|
||||||
|
Options: &resource.ListOptions{
|
||||||
|
Key: dashboardskey,
|
||||||
|
},
|
||||||
|
Fields: []string{
|
||||||
|
"title", "_id",
|
||||||
|
},
|
||||||
|
Federated: []*resource.ResourceKey{
|
||||||
|
folderKey, // This will join in the
|
||||||
|
},
|
||||||
|
Limit: 100000,
|
||||||
|
SortBy: []*resource.ResourceSearchRequest_Sort{
|
||||||
|
{Field: "title", Desc: false},
|
||||||
|
},
|
||||||
|
Facet: map[string]*resource.ResourceSearchRequest_Facet{
|
||||||
|
"region": {
|
||||||
|
Field: "labels.region",
|
||||||
|
Limit: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, []resource.ResourceIndex{foldersIndex}) // << note the folder index matches the federation request
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Nil(t, rsp.Error)
|
||||||
|
require.NotNil(t, rsp.Results)
|
||||||
|
require.NotNil(t, rsp.Facet)
|
||||||
|
|
||||||
|
// Sorted across two indexes
|
||||||
|
sorted := []string{}
|
||||||
|
for _, row := range rsp.Results.Rows {
|
||||||
|
sorted = append(sorted, string(row.Cells[0]))
|
||||||
|
}
|
||||||
|
require.Equal(t, []string{
|
||||||
|
"aaa (dash)",
|
||||||
|
"bbb (dash)",
|
||||||
|
"ccc (dash)",
|
||||||
|
"yyy (folder)",
|
||||||
|
"zzz (folder)",
|
||||||
|
}, sorted)
|
||||||
|
|
||||||
|
resource.AssertTableSnapshot(t, filepath.Join("testdata", "manual-federated.json"), rsp.Results)
|
||||||
|
|
||||||
|
facet, ok := rsp.Facet["region"]
|
||||||
|
require.True(t, ok)
|
||||||
|
disp, err := json.MarshalIndent(facet, "", " ")
|
||||||
|
require.NoError(t, err)
|
||||||
|
// fmt.Printf("%s\n", disp)
|
||||||
|
// NOTE, the west values come from *both* dashboards and folders
|
||||||
|
require.JSONEq(t, `{
|
||||||
|
"field": "labels.region",
|
||||||
|
"total": 3,
|
||||||
|
"missing": 2,
|
||||||
|
"terms": [
|
||||||
|
{
|
||||||
|
"term": "west",
|
||||||
|
"count": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "east",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`, string(disp))
|
||||||
|
})
|
||||||
|
}
|
@ -26,7 +26,11 @@ func (s *StandardDocumentBuilders) GetDocumentBuilders() ([]resource.DocumentBui
|
|||||||
})
|
})
|
||||||
|
|
||||||
return []resource.DocumentBuilderInfo{
|
return []resource.DocumentBuilderInfo{
|
||||||
resource.StandardDocumentBuilder(),
|
// The default builder
|
||||||
|
resource.DocumentBuilderInfo{
|
||||||
|
Builder: resource.StandardDocumentBuilder(),
|
||||||
|
},
|
||||||
|
// Dashboard builder
|
||||||
dashboards,
|
dashboards,
|
||||||
}, err
|
}, err
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ func TestDashboardDocumentBuilder(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Standard
|
// Standard
|
||||||
builder = resource.StandardDocumentBuilder().Builder
|
builder = resource.StandardDocumentBuilder()
|
||||||
doSnapshotTests(t, builder, "folder", key, []string{
|
doSnapshotTests(t, builder, "folder", key, []string{
|
||||||
"aaa",
|
"aaa",
|
||||||
"bbb",
|
"bbb",
|
||||||
|
143
pkg/storage/unified/search/testdata/manual-dashboard.json
vendored
Normal file
143
pkg/storage/unified/search/testdata/manual-dashboard.json
vendored
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
{
|
||||||
|
"metadata": {},
|
||||||
|
"columnDefinitions": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "string",
|
||||||
|
"format": "",
|
||||||
|
"description": "Unique Identifier. {namespace}/{group}/{resource}/{name}",
|
||||||
|
"priority": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "title",
|
||||||
|
"type": "string",
|
||||||
|
"format": "",
|
||||||
|
"description": "Display name for the resource",
|
||||||
|
"priority": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tags",
|
||||||
|
"type": "string",
|
||||||
|
"format": "",
|
||||||
|
"description": "Unique tags",
|
||||||
|
"priority": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "folder",
|
||||||
|
"type": "string",
|
||||||
|
"format": "",
|
||||||
|
"description": "Kubernetes name for the folder",
|
||||||
|
"priority": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rv",
|
||||||
|
"type": "number",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "resource version",
|
||||||
|
"priority": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created",
|
||||||
|
"type": "number",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "created timestamp",
|
||||||
|
"priority": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "schema_version",
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32",
|
||||||
|
"description": "Numeric version saying when the schema was saved",
|
||||||
|
"priority": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "link_count",
|
||||||
|
"type": "number",
|
||||||
|
"format": "int32",
|
||||||
|
"description": "How many links appear on the page",
|
||||||
|
"priority": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "panel_types",
|
||||||
|
"type": "string",
|
||||||
|
"format": "",
|
||||||
|
"description": "How many links appear on the page",
|
||||||
|
"priority": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rows": [
|
||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
"ns/g/dash/ccc",
|
||||||
|
"ccc (dash)",
|
||||||
|
[
|
||||||
|
"aa"
|
||||||
|
],
|
||||||
|
"xxx",
|
||||||
|
3,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
],
|
||||||
|
"object": {
|
||||||
|
"kind": "dash",
|
||||||
|
"apiVersion": "g",
|
||||||
|
"metadata": {
|
||||||
|
"name": "ccc",
|
||||||
|
"namespace": "ns",
|
||||||
|
"creationTimestamp": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
"ns/g/dash/aaa",
|
||||||
|
"bbb (dash)",
|
||||||
|
[
|
||||||
|
"aa",
|
||||||
|
"bb"
|
||||||
|
],
|
||||||
|
"xxx",
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
],
|
||||||
|
"object": {
|
||||||
|
"kind": "dash",
|
||||||
|
"apiVersion": "g",
|
||||||
|
"metadata": {
|
||||||
|
"name": "aaa",
|
||||||
|
"namespace": "ns",
|
||||||
|
"creationTimestamp": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
"ns/g/dash/bbb",
|
||||||
|
"aaa (dash)",
|
||||||
|
[
|
||||||
|
"aa"
|
||||||
|
],
|
||||||
|
"xxx",
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
],
|
||||||
|
"object": {
|
||||||
|
"kind": "dash",
|
||||||
|
"apiVersion": "g",
|
||||||
|
"metadata": {
|
||||||
|
"name": "bbb",
|
||||||
|
"namespace": "ns",
|
||||||
|
"creationTimestamp": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
96
pkg/storage/unified/search/testdata/manual-federated.json
vendored
Normal file
96
pkg/storage/unified/search/testdata/manual-federated.json
vendored
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
{
|
||||||
|
"metadata": {},
|
||||||
|
"columnDefinitions": [
|
||||||
|
{
|
||||||
|
"name": "title",
|
||||||
|
"type": "string",
|
||||||
|
"format": "",
|
||||||
|
"description": "Display name for the resource",
|
||||||
|
"priority": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "string",
|
||||||
|
"format": "",
|
||||||
|
"description": "Unique Identifier. {namespace}/{group}/{resource}/{name}",
|
||||||
|
"priority": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rows": [
|
||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
"aaa (dash)",
|
||||||
|
"ns/g/dash/bbb"
|
||||||
|
],
|
||||||
|
"object": {
|
||||||
|
"kind": "dash",
|
||||||
|
"apiVersion": "g",
|
||||||
|
"metadata": {
|
||||||
|
"name": "bbb",
|
||||||
|
"namespace": "ns",
|
||||||
|
"creationTimestamp": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
"bbb (dash)",
|
||||||
|
"ns/g/dash/aaa"
|
||||||
|
],
|
||||||
|
"object": {
|
||||||
|
"kind": "dash",
|
||||||
|
"apiVersion": "g",
|
||||||
|
"metadata": {
|
||||||
|
"name": "aaa",
|
||||||
|
"namespace": "ns",
|
||||||
|
"creationTimestamp": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
"ccc (dash)",
|
||||||
|
"ns/g/dash/ccc"
|
||||||
|
],
|
||||||
|
"object": {
|
||||||
|
"kind": "dash",
|
||||||
|
"apiVersion": "g",
|
||||||
|
"metadata": {
|
||||||
|
"name": "ccc",
|
||||||
|
"namespace": "ns",
|
||||||
|
"creationTimestamp": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
"yyy (folder)",
|
||||||
|
"ns/g/folder/yyy"
|
||||||
|
],
|
||||||
|
"object": {
|
||||||
|
"kind": "folder",
|
||||||
|
"apiVersion": "g",
|
||||||
|
"metadata": {
|
||||||
|
"name": "yyy",
|
||||||
|
"namespace": "ns",
|
||||||
|
"creationTimestamp": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
"zzz (folder)",
|
||||||
|
"ns/g/folder/zzz"
|
||||||
|
],
|
||||||
|
"object": {
|
||||||
|
"kind": "folder",
|
||||||
|
"apiVersion": "g",
|
||||||
|
"metadata": {
|
||||||
|
"name": "zzz",
|
||||||
|
"namespace": "ns",
|
||||||
|
"creationTimestamp": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
87
pkg/storage/unified/search/testdata/manual-folder.json
vendored
Normal file
87
pkg/storage/unified/search/testdata/manual-folder.json
vendored
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
{
|
||||||
|
"metadata": {},
|
||||||
|
"columnDefinitions": [
|
||||||
|
{
|
||||||
|
"name": "_id",
|
||||||
|
"type": "string",
|
||||||
|
"format": "",
|
||||||
|
"description": "Unique Identifier. {namespace}/{group}/{resource}/{name}",
|
||||||
|
"priority": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "title",
|
||||||
|
"type": "string",
|
||||||
|
"format": "",
|
||||||
|
"description": "Display name for the resource",
|
||||||
|
"priority": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tags",
|
||||||
|
"type": "string",
|
||||||
|
"format": "",
|
||||||
|
"description": "Unique tags",
|
||||||
|
"priority": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "folder",
|
||||||
|
"type": "string",
|
||||||
|
"format": "",
|
||||||
|
"description": "Kubernetes name for the folder",
|
||||||
|
"priority": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rv",
|
||||||
|
"type": "number",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "resource version",
|
||||||
|
"priority": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created",
|
||||||
|
"type": "number",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "created timestamp",
|
||||||
|
"priority": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rows": [
|
||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
"ns/g/folder/yyy",
|
||||||
|
"yyy (folder)",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"object": {
|
||||||
|
"kind": "folder",
|
||||||
|
"apiVersion": "g",
|
||||||
|
"metadata": {
|
||||||
|
"name": "yyy",
|
||||||
|
"namespace": "ns",
|
||||||
|
"creationTimestamp": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
"ns/g/folder/zzz",
|
||||||
|
"zzz (folder)",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
1,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"object": {
|
||||||
|
"kind": "folder",
|
||||||
|
"apiVersion": "g",
|
||||||
|
"metadata": {
|
||||||
|
"name": "zzz",
|
||||||
|
"namespace": "ns",
|
||||||
|
"creationTimestamp": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user