Compare commits

...

26 Commits

Author SHA1 Message Date
grafakus
7f584d143f shrug... 2026-01-14 15:15:51 +01:00
grafakus
e38240369b Update cue files++ 2026-01-14 14:58:36 +01:00
grafakus
26ac64cf1a Merge branch 'main' into grafakus/query-variable-support-multiprops 2026-01-14 14:20:50 +01:00
Fabrizio
ba6a783997 Add Db2 plugin (#116190)
* Add new Db2 plugin

* Fix ID

* Fix ID

* Run `i18n-extract`

* Linting

* Fix ID

* Linting

* Rename plugin

* Fix i18n entry

* Run `yarn i18n-extract`
2026-01-14 14:20:24 +01:00
grafakus
d8777012cb Fix lint issues 2026-01-14 14:19:18 +01:00
grafakus
dfa07d8e10 Fix openapi tests 2026-01-14 14:17:43 +01:00
Andreas Christou
f704b8aa79 Cloud Monitoring: Add support for Google Cloud universe_domain (#115931)
* feat(cloud-monitoring): add support for Google Cloud universe_domain (#110083)

This change introduces support for Google Cloud's `universe_domain`, enabling connections to sovereign cloud environments with custom API endpoints.

- Adds an optional "Universe Domain" field in the Google Cloud Monitoring data source configuration (frontend and backend).
- Allows specifying a custom API domain (e.g., `s3nsapis.fr`) for use in sovereign environments.
- Defaults to `googleapis.com` to ensure backward compatibility for existing configurations.

Signed-off-by: Andreas Christou <andreas.christou@grafana.com>

* Minor docs update

* Doc updates

* Update editor

* Update docs/sources/datasources/google-cloud-monitoring/_index.md

Co-authored-by: Larissa Wandzura <126723338+lwandz13@users.noreply.github.com>

* Lint

* Review

---------

Signed-off-by: Andreas Christou <andreas.christou@grafana.com>
Co-authored-by: Larissa Wandzura <126723338+lwandz13@users.noreply.github.com>
2026-01-14 13:16:09 +00:00
grafakus
dc63eb3314 Regen 2026-01-14 14:03:13 +01:00
Andreas Christou
c1a46fdcb5 Elasticsearch: Decoupling from core (#115900)
* Complete decoupling of backend

- Replace usage of featuremgmt
- Copy simplejson
- Add standalone logic

* Complete frontend decoupling

- Fix imports
- Copy store and reducer logic

* Add required files for full decoupling

* Regen cue

* Prettier

* Remove unneeded script

* Jest fix

* Add jest config

* Lint

* Lit

* Prune suppresions
2026-01-14 12:54:21 +00:00
Cauê Marcondes
7143324229 Elasticsearch: Add support for serverless connections (#114855)
* serverless connecction

* Adding api key

* fix

* addressing pr comments

* fixing tests

* refactoring

* changing to value semantic

* addressing pr comments

* minor changes

---------

Co-authored-by: Lucas Francisco Lopez <lucas.lopez@elastic.co>
2026-01-14 12:51:42 +00:00
grafakus
1e3c6fed55 Merge branch 'main' into grafakus/query-variable-support-multiprops
# Conflicts:
#	apps/dashboard/pkg/apis/dashboard_manifest.go
2026-01-14 13:50:54 +01:00
grafakus
d02201f564 Update schemas 2026-01-14 13:41:21 +01:00
Ryan McKinley
48625d67e5 Chore: update blevesearch dependencies (#116251) 2026-01-14 12:15:19 +00:00
grafakus
2a9377ac02 QueryVariable: Support preview and autocomplete of multi-props 2026-01-14 13:14:32 +01:00
Jack Westbrook
8bad33de4c Grafana/data: Fix theme types schema resolution (#116240)
* fix(grafana-data): copy theme schema json to types so declaration resolves

* refactor(grafana-data): move node scripts out of source code

* feat(grafana-data): generate types for theme schema

* chore(codeowners): update for grafana-data/scripts file move

* feat(grafana-data): put back copy plugin for theme json files

* revert(grafana-data): remove definition output

* feat(grafana-data): make builds great again

* minor tidy up

---------

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
2026-01-14 12:05:23 +00:00
Ryan McKinley
040854c8af Search: Allow query field selection (#116238) 2026-01-14 11:55:05 +00:00
Rafael Bortolon Paulovic
987c1fc6b6 feat(unified): add index scoring model config (#116210)
* feat(unified): add bm25 index scoring model

We want try BM25 scoring model since they have global scoring which we can probably re-use for fan-in/fan-out logic

32d98823c4/docs/scoring.md (global-scoring)

* fix(plugins): update plugin test data
2026-01-14 12:07:53 +01:00
Alejandro Fraenkel
170ac31c5a Alerting: Add alertingNavigationV2 feature toggle (#116215)
feat(alerting): add alertingNavigationV2 feature toggle

Introduces a new feature toggle to enable the improved Alerting navigation
structure with grouped menu items. This toggle will allow:
- Safe incremental rollout of navigation changes
- Quick rollback if issues arise
- Handling BE/FE deployment timing differences

Toggle details:
- Name: alertingNavigationV2
- Stage: Experimental
- Owner: @grafana/alerting-squad
- Default: false (disabled)
- Affects: Both backend (navtree) and frontend (navigation hooks)
2026-01-14 11:58:11 +01:00
Dominik Prokop
0d1e0bc21c PanelMenu: use openInNewTab links extensions API correctly (#116200)
* Extensons: Make links use openInNewTab API

* Use openInNewTab api correctly in the UI

* Bump scenes

* Fx circular dep

* test

* Revert "test"

This reverts commit 8784a7992c.
2026-01-14 11:29:43 +01:00
Natalia Bernarte Oses
afd84f0335 Datagrid: Deprecate panel (#116071)
* deprecate datagrid

* Update docs/sources/visualizations/panels-visualizations/visualizations/datagrid/index.md

Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com>

---------

Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com>
2026-01-14 11:10:51 +01:00
Andres Martinez Gotor
d680537ea1 Advisor: Simplify interface used (#116191) 2026-01-14 11:05:16 +01:00
Bogdan Matei
78d507d285 Dynamic Dashboards: Change the stage of the feature toggle (#116189) 2026-01-14 09:50:37 +00:00
Tito Lins
9d1d0e72c2 Alerting: add sync timer support (#114602)
- add new feature flag to support enabling the dispatcher sync timer on the alertmanager
- this attempts to synchronize the flushes across HA nodes to decrease amount of duplicate notifications

---------

Co-authored-by: Yuri Tseretyan <yuriy.tseretyan@grafana.com>
2026-01-14 10:04:29 +01:00
Konrad Lalik
fd955f90ac Alerting: Enable server-side folder search for GMA rules (#116201)
* Alerting: Support backend filtering for folder search

Updates the Grafana managed rules API and filter logic to support
server-side filtering by folder (namespace).

Changes:
- Add `searchFolder` parameter to `getGrafanaGroups` API endpoint
- Map filter state `namespace` to `searchFolder` in backend filter
- Disable client-side namespace filtering when backend filtering is enabled
- Update tests to verify correct behavior for folder search with backend filters

* Add missing property in filter options

* Update tests
2026-01-14 09:48:07 +01:00
Sonia Aguilar
ccb032f376 Alerting: Single alertmanager contact points versions (#116076)
* POC ssingle AM

* wip

* add query param ?version=2

* wip2

* wip3

* Update logic

* update badges and tests

* remove unsused import

* fix: update NewReceiverView snapshots to include version field

* update translations

* fix: delegate version determination to backend for new integrations

- Remove hardcoded version: 'v1' from defaultChannelValues
- Reset version to undefined when integration type changes
- Backend uses GetCurrentVersion() when no version is provided
- Update snapshots to reflect version handling changes
- Remove unused getDefaultVersionForNotifier function

* update snapshot

* fix(alerting): fix contact point form issues

- Fix empty info alert showing when notifier.dto.info is undefined
- Fix options not loading for new contact points by using default creatable version

* fix(alerting): only show version badge for legacy integrations

* update tests for version badge and getOptionsForVersion changes

* docs: add comment explaining currentVersion field in NotifierDTO

* Show user-friendly 'Legacy' label for legacy integrations

- Replace technical version strings (v0mimir1, v0mimir2) with user-friendly labels
- v0mimir1 -> 'Legacy', v0mimir2 -> 'Legacy v2', etc.
- Technical version is still shown in tooltip for reference
- Add getLegacyVersionLabel() utility function
- Update tests for badge display and utility function

* Add v0mimir2 to test mock for Legacy v2 badge test

* hasLegacyIntegrations now uses isLegacyVersion

- Accept notifiers array to properly check canCreate: false
- No longer relies on version string comparison (v1 check)
- Uses isLegacyVersion for consistent legacy detection
- Update tests to pass notifiers and test correct behavior

* update translations
2026-01-14 08:31:13 +01:00
Alex Khomenko
cf452c167b Provisioning: Do not show the page when the toggle is off (#116206) 2026-01-14 07:41:10 +02:00
193 changed files with 4935 additions and 594 deletions

1
.github/CODEOWNERS vendored
View File

@@ -543,6 +543,7 @@ i18next.config.ts @grafana/grafana-frontend-platform
/packages/grafana-data/tsconfig.json @grafana/grafana-frontend-platform
/packages/grafana-data/test/ @grafana/grafana-frontend-platform
/packages/grafana-data/typings/ @grafana/grafana-frontend-platform
/packages/grafana-data/scripts/ @grafana/grafana-frontend-platform
/packages/grafana-data/src/**/*logs* @grafana/observability-logs
/packages/grafana-data/src/context/plugins/ @grafana/plugins-platform-frontend

View File

@@ -121,6 +121,8 @@ linters:
- '**/pkg/tsdb/zipkin/**/*'
- '**/pkg/tsdb/jaeger/*'
- '**/pkg/tsdb/jaeger/**/*'
- '**/pkg/tsdb/elasticsearch/*'
- '**/pkg/tsdb/elasticsearch/**/*'
deny:
- pkg: github.com/grafana/grafana/pkg/api
desc: Core plugins are not allowed to depend on Grafana core packages

View File

@@ -28,7 +28,7 @@ type check struct {
PluginStore pluginstore.Store
PluginContextProvider PluginContextProvider
PluginClient plugins.Client
PluginRepo repo.Service
PluginRepo checks.PluginInfoGetter
GrafanaVersion string
pluginCanBeInstalledCache map[string]bool
pluginExistsCacheMu sync.RWMutex
@@ -39,7 +39,7 @@ func New(
pluginStore pluginstore.Store,
pluginContextProvider PluginContextProvider,
pluginClient plugins.Client,
pluginRepo repo.Service,
pluginRepo checks.PluginInfoGetter,
grafanaVersion string,
) checks.Check {
return &check{

View File

@@ -15,7 +15,7 @@ import (
type missingPluginStep struct {
PluginStore pluginstore.Store
PluginRepo repo.Service
PluginRepo checks.PluginInfoGetter
GrafanaVersion string
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana-app-sdk/logging"
advisorv0alpha1 "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1"
"github.com/grafana/grafana/pkg/plugins/repo"
)
// Check returns metadata about the check being executed and the list of Steps
@@ -37,3 +38,10 @@ type Step interface {
// Run executes the step for an item and returns a report
Run(ctx context.Context, log logging.Logger, obj *advisorv0alpha1.CheckSpec, item any) ([]advisorv0alpha1.CheckReportFailure, error)
}
// PluginInfoGetter is a minimal interface for retrieving plugin information from a repository.
// It contains only the GetPluginsInfo method used by plugincheck and datasourcecheck.
type PluginInfoGetter interface {
// GetPluginsInfo will return a list of plugins from grafana.com/api/plugins.
GetPluginsInfo(ctx context.Context, options repo.GetPluginsInfoOptions, compatOpts repo.CompatOpts) ([]repo.PluginInfo, error)
}

View File

@@ -17,7 +17,7 @@ const (
func New(
pluginStore pluginstore.Store,
pluginRepo repo.Service,
pluginRepo checks.PluginInfoGetter,
updateChecker pluginchecker.PluginUpdateChecker,
pluginErrorResolver plugins.ErrorResolver,
grafanaVersion string,
@@ -33,7 +33,7 @@ func New(
type check struct {
PluginStore pluginstore.Store
PluginRepo repo.Service
PluginRepo checks.PluginInfoGetter
updateChecker pluginchecker.PluginUpdateChecker
pluginErrorResolver plugins.ErrorResolver
GrafanaVersion string

View File

@@ -800,6 +800,8 @@ VariableOption: {
text: string | [...string]
// Value of the option
value: string | [...string]
// Additional properties for multi-props variables
properties?: {[string]: string}
}
// Query variable specification

View File

@@ -804,6 +804,8 @@ VariableOption: {
text: string | [...string]
// Value of the option
value: string | [...string]
// Additional properties for multi-props variables
properties?: {[string]: string}
}
// Query variable specification

View File

@@ -241,6 +241,8 @@ lineage: schemas: [{
text: string | [...string]
// Value of the option
value: string | [...string]
// Additional properties for multi-props variables
properties?: {[string]: string}
} @cuetsy(kind="interface")
// Options to config when to refresh a variable

View File

@@ -241,6 +241,8 @@ lineage: schemas: [{
text: string | [...string]
// Value of the option
value: string | [...string]
// Additional properties for multi-props variables
properties?: {[string]: string}
} @cuetsy(kind="interface")
// Options to config when to refresh a variable

View File

@@ -804,6 +804,8 @@ VariableOption: {
text: string | [...string]
// Value of the option
value: string | [...string]
// Additional properties for multi-props variables
properties?: {[string]: string}
}
// Query variable specification

View File

@@ -1426,6 +1426,8 @@ type DashboardVariableOption struct {
Text DashboardStringOrArrayOfString `json:"text"`
// Value of the option
Value DashboardStringOrArrayOfString `json:"value"`
// Additional properties for multi-props variables
Properties map[string]string `json:"properties,omitempty"`
}
// NewDashboardVariableOption creates a new DashboardVariableOption object.

View File

@@ -5133,6 +5133,22 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardVariableOption(ref common.Refer
Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardStringOrArrayOfString"),
},
},
"properties": {
SchemaProps: spec.SchemaProps{
Description: "Additional properties for multi-props variables",
Type: []string{"object"},
AdditionalProperties: &spec.SchemaOrBool{
Allows: true,
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
Required: []string{"text", "value"},
},

View File

@@ -808,6 +808,8 @@ VariableOption: {
text: string | [...string]
// Value of the option
value: string | [...string]
// Additional properties for multi-props variables
properties?: {[string]: string}
}
// Query variable specification

View File

@@ -1429,6 +1429,8 @@ type DashboardVariableOption struct {
Text DashboardStringOrArrayOfString `json:"text"`
// Value of the option
Value DashboardStringOrArrayOfString `json:"value"`
// Additional properties for multi-props variables
Properties map[string]string `json:"properties,omitempty"`
}
// NewDashboardVariableOption creates a new DashboardVariableOption object.

View File

@@ -5196,6 +5196,22 @@ func schema_pkg_apis_dashboard_v2beta1_DashboardVariableOption(ref common.Refere
Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardStringOrArrayOfString"),
},
},
"properties": {
SchemaProps: spec.SchemaProps{
Description: "Additional properties for multi-props variables",
Type: []string{"object"},
AdditionalProperties: &spec.SchemaOrBool{
Allows: true,
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
Required: []string{"text", "value"},
},

File diff suppressed because one or more lines are too long

View File

@@ -103,10 +103,11 @@ To configure basic settings for the data source, complete the following steps:
1. Set the data source's basic configuration options:
| Name | Description |
| ----------- | ------------------------------------------------------------------------ |
| **Name** | Sets the name you use to refer to the data source in panels and queries. |
| **Default** | Sets whether the data source is pre-selected for new panels. |
| Name | Description |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Name** | Sets the name you use to refer to the data source in panels and queries. |
| **Default** | Sets whether the data source is pre-selected for new panels. |
| **Universe Domain** | The universe domain to connect to. For more information, refer to [Documentation on universe domains](https://docs.cloud.google.com/python/docs/reference/monitoring/latest/google.cloud.monitoring_v3.services.service_monitoring_service.ServiceMonitoringServiceAsyncClient#google_cloud_monitoring_v3_services_service_monitoring_service_ServiceMonitoringServiceAsyncClient_universe_domain). Defaults to `googleapis.com`. |
### Provision the data source
@@ -129,6 +130,7 @@ datasources:
clientEmail: stackdriver@myproject.iam.gserviceaccount.com
authenticationType: jwt
defaultProject: my-project-name
universeDomain: googleapis.com
secureJsonData:
privateKey: |
-----BEGIN PRIVATE KEY-----
@@ -152,6 +154,7 @@ datasources:
clientEmail: stackdriver@myproject.iam.gserviceaccount.com
authenticationType: jwt
defaultProject: my-project-name
universeDomain: googleapis.com
privateKeyPath: /etc/secrets/gce.pem
```
@@ -166,6 +169,7 @@ datasources:
access: proxy
jsonData:
authenticationType: gce
universeDomain: googleapis.com
```
## Import pre-configured dashboards

View File

@@ -87,6 +87,7 @@ With a Grafana Enterprise license, you also get access to premium data sources,
- [CockroachDB](/grafana/plugins/grafana-cockroachdb-datasource)
- [Databricks](/grafana/plugins/grafana-databricks-datasource)
- [DataDog](/grafana/plugins/grafana-datadog-datasource)
- [IBM Db2](/grafana/plugins/grafana-ibmdb2-datasource)
- [Drone](/grafana/plugins/grafana-drone-datasource)
- [DynamoDB](/grafana/plugins/grafana-dynamodb-datasource/)
- [Dynatrace](/grafana/plugins/grafana-dynatrace-datasource)

View File

@@ -83,6 +83,7 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
| `reportingRetries` | Enables rendering retries for the reporting feature |
| `externalServiceAccounts` | Automatic service account and token setup for plugins |
| `cloudWatchBatchQueries` | Runs CloudWatch metrics queries as separate batches |
| `dashboardNewLayouts` | Enables new dashboard layouts |
| `pdfTables` | Enables generating table data as PDF in reporting |
| `canvasPanelPanZoom` | Allow pan and zoom in canvas panel |
| `alertingSaveStateCompressed` | Enables the compressed protobuf-based alert state storage. Default is enabled. |

View File

@@ -30,7 +30,9 @@ refs:
# Datagrid
{{< docs/experimental product="The datagrid visualization" featureFlag="`enableDatagridEditing`" >}}
{{< admonition type="caution" >}}
Starting with Grafana 12.4, Datagrid is deprecated. It will be removed in version 13.0.
{{< /admonition >}}
Datagrids offer you the ability to create, edit, and fine-tune data within Grafana. As such, this panel can act as a data source for other panels
inside a dashboard.

View File

@@ -3743,46 +3743,21 @@
"count": 1
}
},
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.tsx": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
}
},
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/TermsSettingsEditor.tsx": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
}
},
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/aggregations.ts": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
}
},
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.ts": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
}
},
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.tsx": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
}
},
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/SettingField.tsx": {
"@typescript-eslint/consistent-type-assertions": {
"count": 2
}
},
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/aggregations.ts": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
}
},
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.ts": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
}
},
"public/app/plugins/datasource/elasticsearch/configuration/DataLinks.tsx": {
"no-restricted-syntax": {
"count": 1

23
go.mod
View File

@@ -44,8 +44,8 @@ require (
github.com/beevik/etree v1.4.1 // @grafana/grafana-backend-group
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/blevesearch/bleve/v2 v2.5.0 // @grafana/grafana-search-and-storage
github.com/blevesearch/bleve_index_api v1.2.7 // @grafana/grafana-search-and-storage
github.com/blevesearch/bleve/v2 v2.5.7 // @grafana/grafana-search-and-storage
github.com/blevesearch/bleve_index_api v1.3.0 // @grafana/grafana-search-and-storage
github.com/blugelabs/bluge v0.2.2 // @grafana/grafana-backend-group
github.com/blugelabs/bluge_segment_api v0.2.0 // @grafana/grafana-backend-group
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // @grafana/grafana-backend-group
@@ -365,22 +365,22 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.22.0 // indirect
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/blevesearch/geo v0.1.20 // indirect
github.com/blevesearch/go-faiss v1.0.25 // indirect
github.com/blevesearch/geo v0.2.4 // indirect
github.com/blevesearch/go-faiss v1.0.26 // indirect
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
github.com/blevesearch/gtreap v0.1.1 // indirect
github.com/blevesearch/mmap-go v1.0.4 // indirect
github.com/blevesearch/scorch_segment_api/v2 v2.3.9 // indirect
github.com/blevesearch/scorch_segment_api/v2 v2.3.13 // indirect
github.com/blevesearch/segment v0.9.1 // indirect
github.com/blevesearch/snowballstem v0.9.0 // indirect
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
github.com/blevesearch/vellum v1.1.0 // indirect
github.com/blevesearch/zapx/v11 v11.4.1 // indirect
github.com/blevesearch/zapx/v12 v12.4.1 // indirect
github.com/blevesearch/zapx/v13 v13.4.1 // indirect
github.com/blevesearch/zapx/v14 v14.4.1 // indirect
github.com/blevesearch/zapx/v15 v15.4.1 // indirect
github.com/blevesearch/zapx/v16 v16.2.2 // indirect
github.com/blevesearch/zapx/v11 v11.4.2 // indirect
github.com/blevesearch/zapx/v12 v12.4.2 // indirect
github.com/blevesearch/zapx/v13 v13.4.2 // indirect
github.com/blevesearch/zapx/v14 v14.4.2 // indirect
github.com/blevesearch/zapx/v15 v15.4.2 // indirect
github.com/blevesearch/zapx/v16 v16.2.8 // indirect
github.com/bluele/gcache v0.0.2 // indirect
github.com/blugelabs/ice v1.0.0 // indirect
github.com/blugelabs/ice/v2 v2.0.1 // indirect
@@ -443,7 +443,6 @@ require (
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
github.com/gomodule/redigo v1.8.9 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/cel-go v0.26.1 // indirect

46
go.sum
View File

@@ -931,14 +931,14 @@ github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdn
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/bleve/v2 v2.5.7 h1:2d9YrL5zrX5EBBW++GOaEKjE+NPWeZGaX77IM26m1Z8=
github.com/blevesearch/bleve/v2 v2.5.7/go.mod h1:yj0NlS7ocGC4VOSAedqDDMktdh2935v2CSWOCDMHdSA=
github.com/blevesearch/bleve_index_api v1.3.0 h1:DsMpWVjFNlBw9/6pyWf59XoqcAkhHj3H0UWiQsavb6E=
github.com/blevesearch/bleve_index_api v1.3.0/go.mod h1:xvd48t5XMeeioWQ5/jZvgLrV98flT2rdvEJ3l/ki4Ko=
github.com/blevesearch/geo v0.2.4 h1:ECIGQhw+QALCZaDcogRTNSJYQXRtC8/m8IKiA706cqk=
github.com/blevesearch/geo v0.2.4/go.mod h1:K56Q33AzXt2YExVHGObtmRSFYZKYGv0JEN5mdacJJR8=
github.com/blevesearch/go-faiss v1.0.26 h1:4dRLolFgjPyjkaXwff4NfbZFdE/dfywbzDqporeQvXI=
github.com/blevesearch/go-faiss v1.0.26/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=
@@ -947,8 +947,8 @@ github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+
github.com/blevesearch/mmap-go v1.0.3/go.mod h1:pYvKl/grLQrBxuaRYgoTssa4rVujYYeenDp++2E+yvs=
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/scorch_segment_api/v2 v2.3.13 h1:ZPjv/4VwWvHJZKeMSgScCapOy8+DdmsmRyLmSB88UoY=
github.com/blevesearch/scorch_segment_api/v2 v2.3.13/go.mod h1:ENk2LClTehOuMS8XzN3UxBEErYmtwkE7MAArFTXs9Vc=
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/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
@@ -960,18 +960,18 @@ github.com/blevesearch/vellum v1.0.5/go.mod h1:atE0EH3fvk43zzS7t1YNdNC7DbmcC3uz+
github.com/blevesearch/vellum v1.0.7/go.mod h1:doBZpmRhwTsASB4QdUZANlJvqVAUdUyX0ZK7QJCTeBE=
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/blevesearch/zapx/v11 v11.4.2 h1:l46SV+b0gFN+Rw3wUI1YdMWdSAVhskYuvxlcgpQFljs=
github.com/blevesearch/zapx/v11 v11.4.2/go.mod h1:4gdeyy9oGa/lLa6D34R9daXNUvfMPZqUYjPwiLmekwc=
github.com/blevesearch/zapx/v12 v12.4.2 h1:fzRbhllQmEMUuAQ7zBuMvKRlcPA5ESTgWlDEoB9uQNE=
github.com/blevesearch/zapx/v12 v12.4.2/go.mod h1:TdFmr7afSz1hFh/SIBCCZvcLfzYvievIH6aEISCte58=
github.com/blevesearch/zapx/v13 v13.4.2 h1:46PIZCO/ZuKZYgxI8Y7lOJqX3Irkc3N8W82QTK3MVks=
github.com/blevesearch/zapx/v13 v13.4.2/go.mod h1:knK8z2NdQHlb5ot/uj8wuvOq5PhDGjNYQQy0QDnopZk=
github.com/blevesearch/zapx/v14 v14.4.2 h1:2SGHakVKd+TrtEqpfeq8X+So5PShQ5nW6GNxT7fWYz0=
github.com/blevesearch/zapx/v14 v14.4.2/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8=
github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFxEsp31k=
github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
github.com/blevesearch/zapx/v16 v16.2.8 h1:SlnzF0YGtSlrsOE3oE7EgEX6BIepGpeqxs1IjMbHLQI=
github.com/blevesearch/zapx/v16 v16.2.8/go.mod h1:murSoCJPCk25MqURrcJaBQ1RekuqSCSfMjXH4rHyA14=
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/blugelabs/bluge v0.2.2 h1:gat8CqE6P6tOgeX30XGLOVNTC26cpM2RWVcreXWtYcM=
@@ -1442,8 +1442,6 @@ 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/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
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 v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=

View File

@@ -520,14 +520,40 @@ github.com/benbjohnson/immutable v0.4.0 h1:CTqXbEerYso8YzVPxmWxh2gnoRQbbB9X1quUC
github.com/benbjohnson/immutable v0.4.0/go.mod h1:iAr8OjJGLnLmVUr9MZ/rz4PWUy6Ouc2JLYuMArmvAJM=
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/blevesearch/bleve/v2 v2.5.7 h1:2d9YrL5zrX5EBBW++GOaEKjE+NPWeZGaX77IM26m1Z8=
github.com/blevesearch/bleve/v2 v2.5.7/go.mod h1:yj0NlS7ocGC4VOSAedqDDMktdh2935v2CSWOCDMHdSA=
github.com/blevesearch/bleve_index_api v1.2.8/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
github.com/blevesearch/bleve_index_api v1.2.11 h1:bXQ54kVuwP8hdrXUSOnvTQfgK0KI1+f9A0ITJT8tX1s=
github.com/blevesearch/bleve_index_api v1.2.11/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
github.com/blevesearch/bleve_index_api v1.3.0 h1:DsMpWVjFNlBw9/6pyWf59XoqcAkhHj3H0UWiQsavb6E=
github.com/blevesearch/bleve_index_api v1.3.0/go.mod h1:xvd48t5XMeeioWQ5/jZvgLrV98flT2rdvEJ3l/ki4Ko=
github.com/blevesearch/geo v0.2.4 h1:ECIGQhw+QALCZaDcogRTNSJYQXRtC8/m8IKiA706cqk=
github.com/blevesearch/geo v0.2.4/go.mod h1:K56Q33AzXt2YExVHGObtmRSFYZKYGv0JEN5mdacJJR8=
github.com/blevesearch/go-faiss v1.0.26/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
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/goleveldb v1.0.1 h1:iAtV2Cu5s0GD1lwUiekkFHe2gTMCCNVj2foPclDLIFI=
github.com/blevesearch/goleveldb v1.0.1/go.mod h1:WrU8ltZbIp0wAoig/MHbrPCXSOLpe79nz5lv5nqfYrQ=
github.com/blevesearch/scorch_segment_api/v2 v2.3.10/go.mod h1:Z3e6ChN3qyN35yaQpl00MfI5s8AxUJbpTR/DL8QOQ+8=
github.com/blevesearch/scorch_segment_api/v2 v2.3.13 h1:ZPjv/4VwWvHJZKeMSgScCapOy8+DdmsmRyLmSB88UoY=
github.com/blevesearch/scorch_segment_api/v2 v2.3.13/go.mod h1:ENk2LClTehOuMS8XzN3UxBEErYmtwkE7MAArFTXs9Vc=
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/stempel v0.2.0 h1:CYzVPaScODMvgE9o+kf6D4RJ/VRomyi9uHF+PtB+Afc=
github.com/blevesearch/stempel v0.2.0/go.mod h1:wjeTHqQv+nQdbPuJ/YcvOjTInA2EIc6Ks1FoSUzSLvc=
github.com/blevesearch/vellum v1.0.10/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k=
github.com/blevesearch/zapx/v11 v11.4.2 h1:l46SV+b0gFN+Rw3wUI1YdMWdSAVhskYuvxlcgpQFljs=
github.com/blevesearch/zapx/v11 v11.4.2/go.mod h1:4gdeyy9oGa/lLa6D34R9daXNUvfMPZqUYjPwiLmekwc=
github.com/blevesearch/zapx/v12 v12.4.2 h1:fzRbhllQmEMUuAQ7zBuMvKRlcPA5ESTgWlDEoB9uQNE=
github.com/blevesearch/zapx/v12 v12.4.2/go.mod h1:TdFmr7afSz1hFh/SIBCCZvcLfzYvievIH6aEISCte58=
github.com/blevesearch/zapx/v13 v13.4.2 h1:46PIZCO/ZuKZYgxI8Y7lOJqX3Irkc3N8W82QTK3MVks=
github.com/blevesearch/zapx/v13 v13.4.2/go.mod h1:knK8z2NdQHlb5ot/uj8wuvOq5PhDGjNYQQy0QDnopZk=
github.com/blevesearch/zapx/v14 v14.4.2 h1:2SGHakVKd+TrtEqpfeq8X+So5PShQ5nW6GNxT7fWYz0=
github.com/blevesearch/zapx/v14 v14.4.2/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8=
github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFxEsp31k=
github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
github.com/blevesearch/zapx/v16 v16.2.8 h1:SlnzF0YGtSlrsOE3oE7EgEX6BIepGpeqxs1IjMbHLQI=
github.com/blevesearch/zapx/v16 v16.2.8/go.mod h1:murSoCJPCk25MqURrcJaBQ1RekuqSCSfMjXH4rHyA14=
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
@@ -998,8 +1024,6 @@ github.com/grafana/prometheus-alertmanager v0.25.1-0.20250331083058-4563aec7a975
github.com/grafana/prometheus-alertmanager v0.25.1-0.20250331083058-4563aec7a975/go.mod h1:FGdGvhI40Dq+CTQaSzK9evuve774cgOUdGfVO04OXkw=
github.com/grafana/prometheus-alertmanager v0.25.1-0.20250604130045-92c8f6389b36 h1:AjZ58JRw1ZieFH/SdsddF5BXtsDKt5kSrKNPWrzYz3Y=
github.com/grafana/prometheus-alertmanager v0.25.1-0.20250604130045-92c8f6389b36/go.mod h1:O/QP1BCm0HHIzbKvgMzqb5sSyH88rzkFk84F4TfJjBU=
github.com/grafana/prometheus-alertmanager v0.25.1-0.20260112162805-d29cc9cf7f0f h1:9tRhudagkQO2s61SLFLSziIdCm7XlkfypVKDxpcHokg=
github.com/grafana/prometheus-alertmanager v0.25.1-0.20260112162805-d29cc9cf7f0f/go.mod h1:AsVdCBeDFN9QbgpJg+8voDAcgsW0RmNvBd70ecMMdC0=
github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU=
github.com/grafana/pyroscope/api v1.2.1-0.20250415190842-3ff7247547ae/go.mod h1:6CJ1uXmLZ13ufpO9xE4pST+DyaBt0uszzrV0YnoaVLQ=
github.com/grafana/sqlds/v4 v4.2.4/go.mod h1:BQRjUG8rOqrBI4NAaeoWrIMuoNgfi8bdhCJ+5cgEfLU=
@@ -1092,6 +1116,7 @@ github.com/jon-whit/go-grpc-prometheus v1.4.0/go.mod h1:iTPm+Iuhh3IIqR0iGZ91JJEg
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/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY=
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/jsternberg/zap-logfmt v1.3.0 h1:z1n1AOHVVydOOVuyphbOKyR4NICDQFiJMn1IK5hVQ5Y=
github.com/jsternberg/zap-logfmt v1.3.0/go.mod h1:N3DENp9WNmCZxvkBD/eReWwz1149BK6jEN9cQ4fNwZE=

View File

@@ -82,6 +82,7 @@ module.exports = {
// Decoupled plugins run their own tests so ignoring them here.
'<rootDir>/public/app/plugins/datasource/azuremonitor',
'<rootDir>/public/app/plugins/datasource/cloud-monitoring',
'<rootDir>/public/app/plugins/datasource/elasticsearch',
'<rootDir>/public/app/plugins/datasource/grafana-postgresql-datasource',
'<rootDir>/public/app/plugins/datasource/grafana-pyroscope-datasource',
'<rootDir>/public/app/plugins/datasource/grafana-testdata-datasource',

View File

@@ -237,6 +237,8 @@ lineage: schemas: [{
text: string | [...string]
// Value of the option
value: string | [...string]
// Additional properties for multi-props variables
properties?: {[string]: string}
} @cuetsy(kind="interface")
// Options to config when to refresh a variable

View File

@@ -293,8 +293,8 @@
"@grafana/plugin-ui": "^0.11.1",
"@grafana/prometheus": "workspace:*",
"@grafana/runtime": "workspace:*",
"@grafana/scenes": "v6.52.1",
"@grafana/scenes-react": "v6.52.1",
"@grafana/scenes": "6.52.2",
"@grafana/scenes-react": "6.52.2",
"@grafana/schema": "workspace:*",
"@grafana/sql": "workspace:*",
"@grafana/ui": "workspace:*",

View File

@@ -35,6 +35,14 @@
},
"./test": {
"@grafana-app/source": "./test/index.ts"
},
"./themes/schema.generated.json": {
"@grafana-app/source": "./src/themes/schema.generated.json",
"default": "./dist/esm/themes/schema.generated.json"
},
"./themes/definitions/*.json": {
"@grafana-app/source": "./src/themes/themeDefinitions/*.json",
"default": "./dist/esm/themes/themeDefinitions/*.json"
}
},
"publishConfig": {
@@ -52,7 +60,7 @@
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
"prepack": "cp package.json package.json.bak && node ../../scripts/prepare-npm-package.js",
"postpack": "mv package.json.bak package.json",
"themes-schema": "tsx ./src/themes/scripts/generateSchema.ts"
"themes-schema": "tsx ./scripts/generateSchema.ts"
},
"dependencies": {
"@braintree/sanitize-url": "7.0.1",
@@ -102,6 +110,7 @@
"react-dom": "18.3.1",
"rimraf": "6.0.1",
"rollup": "^4.22.4",
"rollup-plugin-copy": "3.5.0",
"rollup-plugin-esbuild": "6.2.1",
"rollup-plugin-node-externals": "^8.0.0",
"tsx": "^4.21.0",

View File

@@ -1,21 +1,40 @@
import json from '@rollup/plugin-json';
import { createRequire } from 'node:module';
import copy from 'rollup-plugin-copy';
import { entryPoint, plugins, esmOutput, cjsOutput } from '../rollup.config.parts';
const rq = createRequire(import.meta.url);
const pkg = rq('./package.json');
const grafanaDataPlugins = [
...plugins,
copy({
targets: [
{
src: 'src/themes/schema.generated.json',
dest: 'dist/esm/',
},
{
src: 'src/themes/themeDefinitions/*.json',
dest: 'dist/esm/',
},
],
flatten: false,
}),
json(),
];
export default [
{
input: entryPoint,
plugins: [...plugins, json()],
plugins: grafanaDataPlugins,
output: [cjsOutput(pkg, 'grafana-data'), esmOutput(pkg, 'grafana-data')],
treeshake: false,
},
{
input: 'src/unstable.ts',
plugins: [...plugins, json()],
plugins: grafanaDataPlugins,
output: [cjsOutput(pkg, 'grafana-data'), esmOutput(pkg, 'grafana-data')],
treeshake: false,
},

View File

@@ -0,0 +1,22 @@
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { NewThemeOptionsSchema } from '../src/themes/createTheme';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const jsonOut = path.join(__dirname, '..', 'src', 'themes', 'schema.generated.json');
fs.writeFileSync(
jsonOut,
JSON.stringify(
NewThemeOptionsSchema.toJSONSchema({
target: 'draft-07',
}),
undefined,
2
)
);
console.log('Successfully generated theme schema');

View File

@@ -844,7 +844,6 @@ export {
DataLinkConfigOrigin,
SupportedTransformationType,
type InternalDataLink,
type LinkTarget,
type LinkModel,
type LinkModelSupplier,
VariableOrigin,
@@ -852,6 +851,7 @@ export {
VariableSuggestionsScope,
OneClickMode,
} from './types/dataLink';
export { type LinkTarget } from './types/linkTarget';
export {
type Action,
type ActionModel,

View File

@@ -93,7 +93,6 @@ export { DataTransformerID } from '../transformations/transformers/ids';
export { mergeTransformer } from '../transformations/transformers/merge';
export { getThemeById } from '../themes/registry';
export * as experimentalThemeDefinitions from '../themes/themeDefinitions';
export { GrafanaEdition } from '../types/config';
export { SIPrefix } from '../valueFormats/symbolFormatters';

View File

@@ -1,7 +1,18 @@
import { Registry, RegistryItem } from '../utils/Registry';
import { createTheme, NewThemeOptionsSchema } from './createTheme';
import * as extraThemes from './themeDefinitions';
import aubergine from './themeDefinitions/aubergine.json';
import debug from './themeDefinitions/debug.json';
import desertbloom from './themeDefinitions/desertbloom.json';
import gildedgrove from './themeDefinitions/gildedgrove.json';
import gloom from './themeDefinitions/gloom.json';
import mars from './themeDefinitions/mars.json';
import matrix from './themeDefinitions/matrix.json';
import sapphiredusk from './themeDefinitions/sapphiredusk.json';
import synthwave from './themeDefinitions/synthwave.json';
import tron from './themeDefinitions/tron.json';
import victorian from './themeDefinitions/victorian.json';
import zen from './themeDefinitions/zen.json';
import { GrafanaTheme2 } from './types';
export interface ThemeRegistryItem extends RegistryItem {
@@ -9,6 +20,21 @@ export interface ThemeRegistryItem extends RegistryItem {
build: () => GrafanaTheme2;
}
const extraThemes: { [key: string]: unknown } = {
aubergine,
debug,
desertbloom,
gildedgrove,
gloom,
mars,
matrix,
sapphiredusk,
synthwave,
tron,
victorian,
zen,
};
/**
* @internal
* Only for internal use, never use this from a plugin

View File

@@ -1,19 +0,0 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { NewThemeOptionsSchema } from '../createTheme';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
fs.writeFileSync(
path.join(__dirname, '../schema.generated.json'),
JSON.stringify(
NewThemeOptionsSchema.toJSONSchema({
target: 'draft-07',
}),
undefined,
2
)
);

View File

@@ -1,12 +0,0 @@
export { default as aubergine } from './aubergine.json';
export { default as debug } from './debug.json';
export { default as desertbloom } from './desertbloom.json';
export { default as gildedgrove } from './gildedgrove.json';
export { default as mars } from './mars.json';
export { default as matrix } from './matrix.json';
export { default as sapphiredusk } from './sapphiredusk.json';
export { default as synthwave } from './synthwave.json';
export { default as tron } from './tron.json';
export { default as victorian } from './victorian.json';
export { default as zen } from './zen.json';
export { default as gloom } from './gloom.json';

View File

@@ -1,5 +1,6 @@
import { ScopedVars } from './ScopedVars';
import { ExploreCorrelationHelperData, ExplorePanelsState } from './explore';
import { LinkTarget } from './linkTarget';
import { InterpolateFunction } from './panel';
import { DataQuery } from './query';
import { TimeRange } from './time';
@@ -88,8 +89,6 @@ export interface InternalDataLink<T extends DataQuery = any> {
range?: TimeRange;
}
export type LinkTarget = '_blank' | '_self' | undefined;
/**
* Processed Link Model. The values are ready to use
*/

View File

@@ -356,7 +356,7 @@ export interface FeatureToggles {
*/
dashboardScene?: boolean;
/**
* Enables experimental new dashboard layouts
* Enables new dashboard layouts
*/
dashboardNewLayouts?: boolean;
/**
@@ -531,6 +531,10 @@ export interface FeatureToggles {
*/
alertingListViewV2?: boolean;
/**
* Enables the new Alerting navigation structure with improved menu grouping
*/
alertingNavigationV2?: boolean;
/**
* Enables saved searches for alert rules list
*/
alertingSavedSearches?: boolean;
@@ -1251,4 +1255,8 @@ export interface FeatureToggles {
* Enables profiles exemplars support in profiles drilldown
*/
profilesExemplars?: boolean;
/**
* Use synchronized dispatch timer to minimize duplicate notifications across alertmanager HA pods
*/
alertingSyncDispatchTimer?: boolean;
}

View File

@@ -0,0 +1,4 @@
/**
* Target for links - controls whether link opens in new tab or same tab
*/
export type LinkTarget = '_blank' | '_self' | undefined;

View File

@@ -1,7 +1,7 @@
import { ComponentType } from 'react';
import { LinkTarget } from './dataLink';
import { IconName } from './icon';
import { LinkTarget } from './linkTarget';
export interface NavLinkDTO {
id?: string;

View File

@@ -11,6 +11,7 @@ import { DataFrame } from './dataFrame';
import { DataQueryError, DataQueryRequest, DataQueryTimings } from './datasource';
import { FieldConfigSource } from './fieldOverrides';
import { IconName } from './icon';
import { LinkTarget } from './linkTarget';
import { OptionEditorConfig } from './options';
import { PluginMeta } from './plugin';
import { AbsoluteTimeRange, TimeRange, TimeZone } from './time';
@@ -191,6 +192,7 @@ export interface PanelMenuItem {
onClick?: (event: React.MouseEvent) => void;
shortcut?: string;
href?: string;
target?: LinkTarget;
subMenu?: PanelMenuItem[];
}

View File

@@ -91,6 +91,8 @@ export interface VariableOption {
text: string | string[];
value: string | string[];
isNone?: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
properties?: Record<string, any>;
}
export interface IntervalVariableModel extends VariableWithOptions {
@@ -118,6 +120,7 @@ export interface QueryVariableModel extends VariableWithMultiSupport {
definition: string;
sort: VariableSort;
queryValue?: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
query: any;
regex: string;
regexApplyTo?: VariableRegexApplyTo;
@@ -193,6 +196,7 @@ export interface BaseVariableModel {
skipUrlSync: boolean;
index: number;
state: LoadingState;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
error: any | null;
description: string | null;
usedInRepeat?: boolean;

View File

@@ -9,4 +9,4 @@
* and be subject to the standard policies
*/
export { default as themeJsonSchema } from './themes/schema.generated.json';
export {};

View File

@@ -8,7 +8,8 @@
"emitDeclarationOnly": true,
"isolatedModules": true,
"rootDirs": ["."],
"moduleResolution": "bundler"
"moduleResolution": "bundler",
"resolveJsonModule": true
},
"exclude": ["dist/**/*"],
"include": [

View File

@@ -10,7 +10,7 @@
import * as common from '@grafana/schema';
export const pluginVersion = "12.4.0-pre";
export const pluginVersion = "%VERSION%";
export type BucketAggregation = (DateHistogram | Histogram | Terms | Filters | GeoHashGrid | Nested);

View File

@@ -231,6 +231,10 @@ export const defaultVariableModel: Partial<VariableModel> = {
* Option to be selected in a variable.
*/
export interface VariableOption {
/**
* Additional properties for multi-props variables
*/
properties?: Record<string, string>;
/**
* Whether the option is selected or not
*/

View File

@@ -715,7 +715,9 @@ VariableOption: {
// Text to be displayed for the option
text: string | [...string]
// Value of the option
value: string | [...string]
value: string | [...string]
// Additional properties for multi-props variables
properties?: {[string]: string}
}
// Query variable specification

View File

@@ -903,6 +903,8 @@ type VariableOption struct {
Text StringOrArrayOfString `json:"text"`
// Value of the option
Value StringOrArrayOfString `json:"value"`
// Additional properties for multi-props variables
Properties map[string]string `json:"properties,omitempty"`
}
// NewVariableOption creates a new VariableOption object.

View File

@@ -574,8 +574,8 @@ var (
},
{
Name: "dashboardNewLayouts",
Description: "Enables experimental new dashboard layouts",
Stage: FeatureStageExperimental,
Description: "Enables new dashboard layouts",
Stage: FeatureStagePublicPreview,
FrontendOnly: false, // The restore backend feature changes behavior based on this flag
Owner: grafanaDashboardsSquad,
},
@@ -879,6 +879,13 @@ var (
Owner: grafanaAlertingSquad,
FrontendOnly: true,
},
{
Name: "alertingNavigationV2",
Description: "Enables the new Alerting navigation structure with improved menu grouping",
Stage: FeatureStageExperimental,
Owner: grafanaAlertingSquad,
FrontendOnly: false,
},
{
Name: "alertingSavedSearches",
Description: "Enables saved searches for alert rules list",
@@ -981,7 +988,8 @@ var (
Stage: FeatureStageDeprecated,
Owner: grafanaPartnerPluginsSquad,
Expression: "true", // Enabled by default for now
}, {
},
{
Name: "alertingFilterV2",
Description: "Enable the new alerting search experience",
Stage: FeatureStageExperimental,
@@ -2069,6 +2077,14 @@ var (
Owner: grafanaObservabilityTracesAndProfilingSquad,
FrontendOnly: false,
},
{
Name: "alertingSyncDispatchTimer",
Description: "Use synchronized dispatch timer to minimize duplicate notifications across alertmanager HA pods",
Stage: FeatureStageExperimental,
Owner: grafanaAlertingSquad,
RequiresRestart: true,
HideFromDocs: true,
},
}
)

View File

@@ -79,7 +79,7 @@ annotationPermissionUpdate,GA,@grafana/identity-access-team,false,false,false
dashboardSceneForViewers,GA,@grafana/dashboards-squad,false,false,true
dashboardSceneSolo,GA,@grafana/dashboards-squad,false,false,true
dashboardScene,GA,@grafana/dashboards-squad,false,false,true
dashboardNewLayouts,experimental,@grafana/dashboards-squad,false,false,false
dashboardNewLayouts,preview,@grafana/dashboards-squad,false,false,false
dashboardUndoRedo,experimental,@grafana/dashboards-squad,false,false,true
unlimitedLayoutsNesting,experimental,@grafana/dashboards-squad,false,false,true
drilldownRecommendations,experimental,@grafana/dashboards-squad,false,false,true
@@ -121,6 +121,7 @@ dashboardLibrary,experimental,@grafana/sharing-squad,false,false,false
suggestedDashboards,experimental,@grafana/sharing-squad,false,false,false
dashboardTemplates,preview,@grafana/sharing-squad,false,false,false
alertingListViewV2,privatePreview,@grafana/alerting-squad,false,false,true
alertingNavigationV2,experimental,@grafana/alerting-squad,false,false,false
alertingSavedSearches,experimental,@grafana/alerting-squad,false,false,true
alertingDisableSendAlertsExternal,experimental,@grafana/alerting-squad,false,false,false
preserveDashboardStateWhenNavigating,experimental,@grafana/dashboards-squad,false,false,false
@@ -280,3 +281,4 @@ multiPropsVariables,experimental,@grafana/dashboards-squad,false,false,true
smoothingTransformation,experimental,@grafana/datapro,false,false,true
secretsManagementAppPlatformAwsKeeper,experimental,@grafana/grafana-operator-experience-squad,false,false,false
profilesExemplars,experimental,@grafana/observability-traces-and-profiling,false,false,false
alertingSyncDispatchTimer,experimental,@grafana/alerting-squad,false,true,false
1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
79 dashboardSceneForViewers GA @grafana/dashboards-squad false false true
80 dashboardSceneSolo GA @grafana/dashboards-squad false false true
81 dashboardScene GA @grafana/dashboards-squad false false true
82 dashboardNewLayouts experimental preview @grafana/dashboards-squad false false false
83 dashboardUndoRedo experimental @grafana/dashboards-squad false false true
84 unlimitedLayoutsNesting experimental @grafana/dashboards-squad false false true
85 drilldownRecommendations experimental @grafana/dashboards-squad false false true
121 suggestedDashboards experimental @grafana/sharing-squad false false false
122 dashboardTemplates preview @grafana/sharing-squad false false false
123 alertingListViewV2 privatePreview @grafana/alerting-squad false false true
124 alertingNavigationV2 experimental @grafana/alerting-squad false false false
125 alertingSavedSearches experimental @grafana/alerting-squad false false true
126 alertingDisableSendAlertsExternal experimental @grafana/alerting-squad false false false
127 preserveDashboardStateWhenNavigating experimental @grafana/dashboards-squad false false false
281 smoothingTransformation experimental @grafana/datapro false false true
282 secretsManagementAppPlatformAwsKeeper experimental @grafana/grafana-operator-experience-squad false false false
283 profilesExemplars experimental @grafana/observability-traces-and-profiling false false false
284 alertingSyncDispatchTimer experimental @grafana/alerting-squad false true false

View File

@@ -260,7 +260,7 @@ const (
FlagAnnotationPermissionUpdate = "annotationPermissionUpdate"
// FlagDashboardNewLayouts
// Enables experimental new dashboard layouts
// Enables new dashboard layouts
FlagDashboardNewLayouts = "dashboardNewLayouts"
// FlagPdfTables
@@ -371,6 +371,10 @@ const (
// Enables a flow to get started with a new dashboard from a template
FlagDashboardTemplates = "dashboardTemplates"
// FlagAlertingNavigationV2
// Enables the new Alerting navigation structure with improved menu grouping
FlagAlertingNavigationV2 = "alertingNavigationV2"
// FlagAlertingDisableSendAlertsExternal
// Disables the ability to send alerts to an external Alertmanager datasource.
FlagAlertingDisableSendAlertsExternal = "alertingDisableSendAlertsExternal"
@@ -789,4 +793,8 @@ const (
// FlagProfilesExemplars
// Enables profiles exemplars support in profiles drilldown
FlagProfilesExemplars = "profilesExemplars"
// FlagAlertingSyncDispatchTimer
// Use synchronized dispatch timer to minimize duplicate notifications across alertmanager HA pods
FlagAlertingSyncDispatchTimer = "alertingSyncDispatchTimer"
)

View File

@@ -348,6 +348,18 @@
"expression": "true"
}
},
{
"metadata": {
"name": "alertingNavigationV2",
"resourceVersion": "1768320918269",
"creationTimestamp": "2026-01-13T16:15:18Z"
},
"spec": {
"description": "Enables the new Alerting navigation structure with improved menu grouping",
"stage": "experimental",
"codeowner": "@grafana/alerting-squad"
}
},
{
"metadata": {
"name": "alertingNotificationHistory",
@@ -511,6 +523,20 @@
"frontend": true
}
},
{
"metadata": {
"name": "alertingSyncDispatchTimer",
"resourceVersion": "1766161788928",
"creationTimestamp": "2025-12-19T16:29:48Z"
},
"spec": {
"description": "Use synchronized dispatch timer to minimize duplicate notifications across alertmanager HA pods",
"stage": "experimental",
"codeowner": "@grafana/alerting-squad",
"requiresRestart": true,
"hideFromDocs": true
}
},
{
"metadata": {
"name": "alertingTriage",
@@ -662,7 +688,8 @@
"metadata": {
"name": "auditLoggingAppPlatform",
"resourceVersion": "1767013056996",
"creationTimestamp": "2025-12-29T12:57:36Z"
"creationTimestamp": "2025-12-29T12:57:36Z",
"deletionTimestamp": "2026-01-06T09:18:36Z"
},
"spec": {
"description": "Enable audit logging with Kubernetes under app platform",
@@ -1015,12 +1042,15 @@
{
"metadata": {
"name": "dashboardNewLayouts",
"resourceVersion": "1764664939750",
"creationTimestamp": "2024-10-23T08:55:45Z"
"resourceVersion": "1768382835527",
"creationTimestamp": "2024-10-23T08:55:45Z",
"annotations": {
"grafana.app/updatedTimestamp": "2026-01-14 09:27:15.527103 +0000 UTC"
}
},
"spec": {
"description": "Enables experimental new dashboard layouts",
"stage": "experimental",
"description": "Enables new dashboard layouts",
"stage": "preview",
"codeowner": "@grafana/dashboards-squad"
}
},

View File

@@ -54,8 +54,7 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink
}
//nolint:staticcheck // not yet migrated to OpenFeature
if c.HasRole(identity.RoleAdmin) &&
(s.cfg.StackID == "" || // show OnPrem even when provisioning is disabled
s.features.IsEnabledGlobally(featuremgmt.FlagProvisioning)) {
s.features.IsEnabledGlobally(featuremgmt.FlagProvisioning) {
generalNodeLinks = append(generalNodeLinks, &navtree.NavLink{
Text: "Provisioning",
Id: "provisioning",

View File

@@ -213,6 +213,9 @@ func (ng *AlertNG) init() error {
SkipVerify: ng.Cfg.Smtp.SkipVerify,
StaticHeaders: ng.Cfg.Smtp.StaticHeaders,
}
runtimeConfig := remoteClient.RuntimeConfig{
DispatchTimer: notifier.GetDispatchTimer(ng.FeatureToggles).String(),
}
cfg := remote.AlertmanagerConfig{
BasicAuthPassword: ng.Cfg.UnifiedAlerting.RemoteAlertmanager.Password,
@@ -222,6 +225,7 @@ func (ng *AlertNG) init() error {
ExternalURL: ng.Cfg.AppURL,
SmtpConfig: smtpCfg,
Timeout: ng.Cfg.UnifiedAlerting.RemoteAlertmanager.Timeout,
RuntimeConfig: runtimeConfig,
}
autogenFn := func(ctx context.Context, logger log.Logger, orgID int64, cfg *definitions.PostableApiAlertingConfig, invalidReceiverAction notifier.InvalidReceiversAction) error {
return notifier.AddAutogenConfig(ctx, logger, ng.store, orgID, cfg, invalidReceiverAction, ng.FeatureToggles)

View File

@@ -33,6 +33,9 @@ const (
// How long we keep silences in the kvstore after they've expired.
silenceRetention = 5 * 24 * time.Hour
// How long we keep flushes in the kvstore after they've expired.
flushRetention = 5 * 24 * time.Hour
)
type AlertingStore interface {
@@ -44,8 +47,10 @@ type AlertingStore interface {
type stateStore interface {
SaveSilences(ctx context.Context, st alertingNotify.State) (int64, error)
SaveNotificationLog(ctx context.Context, st alertingNotify.State) (int64, error)
SaveFlushLog(ctx context.Context, st alertingNotify.State) (int64, error)
GetSilences(ctx context.Context) (string, error)
GetNotificationLog(ctx context.Context) (string, error)
GetFlushLog(ctx context.Context) (string, error)
}
type alertmanager struct {
@@ -101,6 +106,10 @@ func NewAlertmanager(ctx context.Context, orgID int64, cfg *setting.Cfg, store A
if err != nil {
return nil, err
}
flushLog, err := stateStore.GetFlushLog(ctx)
if err != nil {
return nil, err
}
silencesOptions := maintenanceOptions{
initialState: silences,
@@ -123,12 +132,29 @@ func NewAlertmanager(ctx context.Context, orgID int64, cfg *setting.Cfg, store A
}
l := log.New("ngalert.notifier")
dispatchTimer := GetDispatchTimer(featureToggles)
var flushLogOptions *maintenanceOptions
if dispatchTimer == alertingNotify.DispatchTimerSync {
flushLogOptions = &maintenanceOptions{
initialState: flushLog,
retention: flushRetention,
maintenanceFrequency: maintenanceInterval,
maintenanceFunc: func(state alertingNotify.State) (int64, error) {
// Detached context here is to make sure that when the service is shut down the persist operation is executed.
return stateStore.SaveFlushLog(context.Background(), state)
},
}
}
opts := alertingNotify.GrafanaAlertmanagerOpts{
ExternalURL: cfg.AppURL,
AlertStoreCallback: nil,
PeerTimeout: cfg.UnifiedAlerting.HAPeerTimeout,
Silences: silencesOptions,
Nflog: nflogOptions,
FlushLog: flushLogOptions,
DispatchTimer: dispatchTimer,
Limits: alertingNotify.Limits{
MaxSilences: cfg.UnifiedAlerting.AlertmanagerMaxSilencesCount,
MaxSilenceSizeBytes: cfg.UnifiedAlerting.AlertmanagerMaxSilenceSizeBytes,

View File

@@ -0,0 +1,16 @@
package notifier
import (
alertingNotify "github.com/grafana/alerting/notify"
"github.com/grafana/grafana/pkg/services/featuremgmt"
)
// GetDispatchTimer returns the appropriate dispatch timer based on feature toggles.
func GetDispatchTimer(features featuremgmt.FeatureToggles) (dt alertingNotify.DispatchTimer) {
//nolint:staticcheck // not yet migrated to OpenFeature
enabled := features.IsEnabledGlobally(featuremgmt.FlagAlertingSyncDispatchTimer)
if enabled {
dt = alertingNotify.DispatchTimerSync
}
return
}

View File

@@ -0,0 +1,36 @@
package notifier
import (
"testing"
alertingNotify "github.com/grafana/alerting/notify"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/stretchr/testify/require"
)
func TestGetDispatchTimer(t *testing.T) {
tests := []struct {
name string
featureFlagValue bool
expected alertingNotify.DispatchTimer
}{
{
name: "feature flag enabled returns sync timer",
featureFlagValue: true,
expected: alertingNotify.DispatchTimerSync,
},
{
name: "feature flag disabled returns default timer",
featureFlagValue: false,
expected: alertingNotify.DispatchTimerDefault,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
features := featuremgmt.WithFeatures(featuremgmt.FlagAlertingSyncDispatchTimer, tt.featureFlagValue)
result := GetDispatchTimer(features)
require.Equal(t, tt.expected, result)
})
}
}

View File

@@ -15,6 +15,7 @@ const (
KVNamespace = "alertmanager"
NotificationLogFilename = "notifications"
SilencesFilename = "silences"
FlushLogFilename = "flushes"
)
// FileStore is in charge of persisting the alertmanager files to the database.
@@ -42,6 +43,10 @@ func (fileStore *FileStore) GetNotificationLog(ctx context.Context) (string, err
return fileStore.contentFor(ctx, NotificationLogFilename)
}
func (fileStore *FileStore) GetFlushLog(ctx context.Context) (string, error) {
return fileStore.contentFor(ctx, FlushLogFilename)
}
// contentFor returns the content for the given Alertmanager kvstore key.
func (fileStore *FileStore) contentFor(ctx context.Context, filename string) (string, error) {
// Then, let's attempt to read it from the database.
@@ -74,6 +79,11 @@ func (fileStore *FileStore) SaveNotificationLog(ctx context.Context, st alerting
return fileStore.persist(ctx, NotificationLogFilename, st)
}
// SaveFlushLog saves the flush log to the database and returns the size of the unencoded state.
func (fileStore *FileStore) SaveFlushLog(ctx context.Context, st alertingNotify.State) (int64, error) {
return fileStore.persist(ctx, FlushLogFilename, st)
}
// persist takes care of persisting the binary representation of internal state to the database as a base64 encoded string.
func (fileStore *FileStore) persist(ctx context.Context, filename string, st alertingNotify.State) (int64, error) {
var size int64

View File

@@ -106,3 +106,48 @@ func TestFileStore_NotificationLog(t *testing.T) {
t.Errorf("Unexpected Diff: %v", cmp.Diff(newState, decoded))
}
}
func TestFileStore_FlushLog(t *testing.T) {
store := fakes.NewFakeKVStore(t)
ctx := context.Background()
var orgId int64 = 1
// Initialize kvstore with empty flush log state.
initialState := flushLogState{} // FlushLog uses the same structure as nflog
decodedState, err := initialState.MarshalBinary()
require.NoError(t, err)
encodedState := base64.StdEncoding.EncodeToString(decodedState)
err = store.Set(ctx, orgId, KVNamespace, FlushLogFilename, encodedState)
require.NoError(t, err)
fs := NewFileStore(orgId, store)
// Load initial (empty).
flushLog, err := fs.GetFlushLog(ctx)
require.NoError(t, err)
decoded, err := decodeFlushLogState(strings.NewReader(flushLog))
require.NoError(t, err)
if !cmp.Equal(initialState, decoded) {
t.Errorf("Unexpected Diff: %v", cmp.Diff(initialState, decoded))
}
// Save new flush log state.
now := time.Now()
oneHour := now.Add(time.Hour)
v1 := createFlushLog(1, now, oneHour)
v2 := createFlushLog(2, now, oneHour)
newState := flushLogState{1: v1, 2: v2}
size, err := fs.SaveFlushLog(ctx, newState)
require.NoError(t, err)
require.Greater(t, size, int64(0))
// Load new.
flushLog, err = fs.GetFlushLog(ctx)
require.NoError(t, err)
decoded, err = decodeFlushLogState(strings.NewReader(flushLog))
require.NoError(t, err)
if !cmp.Equal(newState, decoded) {
t.Errorf("Unexpected Diff: %v", cmp.Diff(newState, decoded))
}
}

View File

@@ -82,6 +82,7 @@ type Alertmanager interface {
type ExternalState struct {
Silences []byte
Nflog []byte
FlushLog []byte
}
// StateMerger describes a type that is able to merge external state (nflog, silences) with its own.
@@ -378,7 +379,7 @@ func (moa *MultiOrgAlertmanager) SyncAlertmanagersForOrgs(ctx context.Context, o
func (moa *MultiOrgAlertmanager) cleanupOrphanLocalOrgState(ctx context.Context,
activeOrganizations map[int64]struct{},
) {
storedFiles := []string{NotificationLogFilename, SilencesFilename}
storedFiles := []string{NotificationLogFilename, SilencesFilename, FlushLogFilename}
for _, fileName := range storedFiles {
keys, err := moa.kvStore.Keys(ctx, kvstore.AllOrganizations, KVNamespace, fileName)
if err != nil {

View File

@@ -5,5 +5,8 @@ func (am *alertmanager) MergeState(state ExternalState) error {
if err := am.Base.MergeNflog(state.Nflog); err != nil {
return err
}
return am.Base.MergeSilences(state.Silences)
if err := am.Base.MergeSilences(state.Silences); err != nil {
return err
}
return am.Base.MergeFlushLog(state.FlushLog)
}

View File

@@ -11,6 +11,7 @@ import (
"time"
"github.com/matttproud/golang_protobuf_extensions/pbutil"
"github.com/prometheus/alertmanager/flushlog/flushlogpb"
"github.com/prometheus/alertmanager/nflog/nflogpb"
"github.com/prometheus/alertmanager/silence/silencepb"
"github.com/prometheus/common/model"
@@ -228,15 +229,13 @@ func (f *FakeOrgStore) FetchOrgIds(_ context.Context) ([]int64, error) {
return f.orgs, nil
}
type NoValidation struct {
}
type NoValidation struct{}
func (n NoValidation) Validate(_ models.NotificationSettings) error {
return nil
}
type RejectingValidation struct {
}
type RejectingValidation struct{}
func (n RejectingValidation) Validate(s models.NotificationSettings) error {
return ErrorReceiverDoesNotExist{ErrorReferenceInvalid: ErrorReferenceInvalid{Reference: s.Receiver}}
@@ -365,6 +364,51 @@ func createNotificationLog(groupKey string, receiverName string, sentAt, expires
}
}
// https://github.com/grafana/prometheus-alertmanager/blob/main/flushlog/flushlog.go#L136-L136
type flushLogState map[uint64]*flushlogpb.MeshFlushLog
func (s flushLogState) MarshalBinary() ([]byte, error) {
var buf bytes.Buffer
for _, e := range s {
if _, err := pbutil.WriteDelimited(&buf, e); err != nil {
return nil, err
}
}
return buf.Bytes(), nil
}
func createFlushLog(groupFingerprint uint64, ts, expiresAt time.Time) *flushlogpb.MeshFlushLog {
return &flushlogpb.MeshFlushLog{
FlushLog: &flushlogpb.FlushLog{
GroupFingerprint: groupFingerprint,
Timestamp: ts,
},
ExpiresAt: expiresAt,
}
}
// decodeFlushLogState copied from decodeState in prometheus-alertmanager/flushlog/flushlog.go
func decodeFlushLogState(r io.Reader) (flushLogState, error) {
st := flushLogState{}
for {
var e flushlogpb.MeshFlushLog
_, err := pbutil.ReadDelimited(r, &e)
if err == nil {
if e.FlushLog == nil || e.FlushLog.GroupFingerprint == 0 || e.FlushLog.Timestamp.IsZero() {
return nil, errInvalidState
}
st[e.FlushLog.GroupFingerprint] = &e
continue
}
if errors.Is(err, io.EOF) {
break
}
return nil, err
}
return st, nil
}
type call struct {
Method string
Args []interface{}

View File

@@ -47,6 +47,7 @@ import (
type stateStore interface {
GetSilences(ctx context.Context) (string, error)
GetNotificationLog(ctx context.Context) (string, error)
GetFlushLog(ctx context.Context) (string, error)
}
// AutogenFn is a function that adds auto-generated routes to a configuration.
@@ -86,6 +87,8 @@ type Alertmanager struct {
promoteConfig bool
externalURL string
runtimeConfig remoteClient.RuntimeConfig
}
type AlertmanagerConfig struct {
@@ -111,6 +114,9 @@ type AlertmanagerConfig struct {
// Timeout for the HTTP client.
Timeout time.Duration
// RuntimeConfig specifies runtime behavior settings for the remote Alertmanager.
RuntimeConfig remoteClient.RuntimeConfig
}
func (cfg *AlertmanagerConfig) Validate() error {
@@ -203,6 +209,7 @@ func NewAlertmanager(ctx context.Context, cfg AlertmanagerConfig, store stateSto
externalURL: cfg.ExternalURL,
promoteConfig: cfg.PromoteConfig,
smtp: cfg.SmtpConfig,
runtimeConfig: cfg.RuntimeConfig,
}
// Parse the default configuration once and remember its hash so we can compare it later.
@@ -331,10 +338,11 @@ func (am *Alertmanager) buildConfiguration(ctx context.Context, raw []byte, crea
AlertmanagerConfig: mergeResult.Config,
Templates: templates,
},
CreatedAt: createdAtEpoch,
Promoted: am.promoteConfig,
ExternalURL: am.externalURL,
SmtpConfig: am.smtp,
CreatedAt: createdAtEpoch,
Promoted: am.promoteConfig,
ExternalURL: am.externalURL,
SmtpConfig: am.smtp,
RuntimeConfig: am.runtimeConfig,
}
cfgHash, err := calculateUserGrafanaConfigHash(payload)
@@ -388,6 +396,8 @@ func (am *Alertmanager) GetRemoteState(ctx context.Context) (notifier.ExternalSt
rs.Silences = p.Data
case "nfl":
rs.Nflog = p.Data
case "fls":
rs.FlushLog = p.Data
default:
return rs, fmt.Errorf("unknown part key %q", p.Key)
}
@@ -677,6 +687,12 @@ func (am *Alertmanager) getFullState(ctx context.Context) (string, error) {
}
parts = append(parts, alertingClusterPB.Part{Key: notifier.NotificationLogFilename, Data: []byte(notificationLog)})
flushLog, err := am.state.GetFlushLog(ctx)
if err != nil {
return "", fmt.Errorf("error getting flush log: %w", err)
}
parts = append(parts, alertingClusterPB.Part{Key: notifier.FlushLogFilename, Data: []byte(flushLog)})
fs := alertingClusterPB.FullState{
Parts: parts,
}

View File

@@ -29,6 +29,10 @@ func (u *GrafanaAlertmanagerConfig) MarshalJSON() ([]byte, error) {
return definition.MarshalJSONWithSecrets((*cfg)(u))
}
type RuntimeConfig struct {
DispatchTimer string `json:"dispatch_timer"`
}
type UserGrafanaConfig struct {
GrafanaAlertmanagerConfig GrafanaAlertmanagerConfig `json:"configuration"`
Hash string `json:"configuration_hash"`
@@ -37,6 +41,7 @@ type UserGrafanaConfig struct {
Promoted bool `json:"promoted"`
ExternalURL string `json:"external_url"`
SmtpConfig SmtpConfig `json:"smtp_config"`
RuntimeConfig RuntimeConfig `json:"runtime_config"`
}
func (mc *Mimir) GetGrafanaAlertmanagerConfig(ctx context.Context) (*UserGrafanaConfig, error) {

View File

@@ -600,6 +600,7 @@ type Cfg struct {
IndexRebuildInterval time.Duration
IndexCacheTTL time.Duration
IndexMinUpdateInterval time.Duration // Don't update index if it was updated less than this interval ago.
IndexScoringModel string // Note: Temporary config to switch the index scoring model and will be removed soon.
MaxFileIndexAge time.Duration // Max age of file-based indexes. Index older than this will be rebuilt asynchronously.
MinFileIndexBuildVersion string // Minimum version of Grafana that built the file-based index. If index was built with older Grafana, it will be rebuilt asynchronously.
EnableSharding bool

View File

@@ -123,6 +123,10 @@ func (cfg *Cfg) setUnifiedStorageConfig() {
cfg.IndexRebuildInterval = section.Key("index_rebuild_interval").MustDuration(24 * time.Hour)
cfg.IndexCacheTTL = section.Key("index_cache_ttl").MustDuration(10 * time.Minute)
cfg.IndexMinUpdateInterval = section.Key("index_min_update_interval").MustDuration(0)
cfg.IndexScoringModel = section.Key("index_scoring_model").MustString("")
if cfg.IndexScoringModel != "" {
cfg.Logger.Info("Index scoring model set", "model", cfg.IndexScoringModel)
}
cfg.SprinklesApiServer = section.Key("sprinkles_api_server").String()
cfg.SprinklesApiServerPageLimit = section.Key("sprinkles_api_server_page_limit").MustInt(10000)
cfg.CACertPath = section.Key("ca_cert_path").String()

View File

@@ -9,11 +9,13 @@ import "resource.proto";
// Unlike the ResourceStore, this service can be exposed to clients directly
// It should be implemented with efficient indexes and does not need read-after-write semantics
service ResourceIndex {
// Query for documents
rpc Search(ResourceSearchRequest) returns (ResourceSearchResponse);
// Get the resource stats
rpc GetStats(ResourceStatsRequest) returns (ResourceStatsResponse);
// Rebuild the search index
rpc RebuildIndexes(RebuildIndexesRequest) returns (RebuildIndexesResponse);
}
@@ -49,6 +51,20 @@ message ResourceStatsResponse {
repeated Stats stats = 2;
}
// This controls what query and analyzers are applied to the specified field
// See: https://blevesearch.com/docs/Analyzers/
enum QueryFieldType {
// Picks a reasonable analyzer given the input. Currently this always uses TEXT
// In the future, it may change to depend on the indexed field type
DEFAULT = 0;
// Use free text analyzer. The query is broken into a normalized set of tokens
TEXT = 1;
// The query must exactly match the indexed token
KEYWORD = 2;
// Like a text query, but the position and offsets influence the score
PHRASE = 3;
}
// Search within a single resource
message ResourceSearchRequest {
message Sort {
@@ -64,6 +80,18 @@ message ResourceSearchRequest {
// date queries
}
// Defines the field in the index to query
// Boost is optional, and allows weighting the field higher in the results
message QueryField {
// The field name in the index to query
string name = 1;
QueryFieldType type = 2;
// Boost value for this field
float boost = 3;
}
// The key must include namespace + group + resource
ListOptions options = 1;
@@ -99,6 +127,9 @@ message ResourceSearchRequest {
int64 page = 11;
int64 permission = 12;
// Optionally specify which fields are included in the query
repeated QueryField query_fields = 13;
}
message ResourceSearchResponse {

View File

@@ -290,7 +290,6 @@ const SEARCH_FIELD_NAMESPACE = "namespace"
const SEARCH_FIELD_NAME = "name"
const SEARCH_FIELD_RV = "rv"
const SEARCH_FIELD_TITLE = "title"
const SEARCH_FIELD_TITLE_NGRAM = "title_ngram"
const SEARCH_FIELD_TITLE_PHRASE = "title_phrase" // filtering/sorting on title by full phrase
const SEARCH_FIELD_DESCRIPTION = "description"
const SEARCH_FIELD_TAGS = "tags"

View File

@@ -21,6 +21,65 @@ const (
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// This controls what query and analyzers are applied to the specified field
// See: https://blevesearch.com/docs/Analyzers/
type QueryFieldType int32
const (
// Picks a reasonable analyzer given the input. Currently this always uses TEXT
// In the future, it may change to depend on the indexed field type
QueryFieldType_DEFAULT QueryFieldType = 0
// Use free text analyzer. The query is broken into a normalized set of tokens
QueryFieldType_TEXT QueryFieldType = 1
// The query must exactly match the indexed token
QueryFieldType_KEYWORD QueryFieldType = 2
// Like a text query, but the position and offsets influence the score
QueryFieldType_PHRASE QueryFieldType = 3
)
// Enum value maps for QueryFieldType.
var (
QueryFieldType_name = map[int32]string{
0: "DEFAULT",
1: "TEXT",
2: "KEYWORD",
3: "PHRASE",
}
QueryFieldType_value = map[string]int32{
"DEFAULT": 0,
"TEXT": 1,
"KEYWORD": 2,
"PHRASE": 3,
}
)
func (x QueryFieldType) Enum() *QueryFieldType {
p := new(QueryFieldType)
*p = x
return p
}
func (x QueryFieldType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (QueryFieldType) Descriptor() protoreflect.EnumDescriptor {
return file_search_proto_enumTypes[0].Descriptor()
}
func (QueryFieldType) Type() protoreflect.EnumType {
return &file_search_proto_enumTypes[0]
}
func (x QueryFieldType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use QueryFieldType.Descriptor instead.
func (QueryFieldType) EnumDescriptor() ([]byte, []int) {
return file_search_proto_rawDescGZIP(), []int{0}
}
// Get statistics across multiple resources
// For these queries, we do not need authorization to see the actual values
type ResourceStatsRequest struct {
@@ -165,10 +224,12 @@ type ResourceSearchRequest struct {
// the return fields (empty will return everything)
Fields []string `protobuf:"bytes,8,rep,name=fields,proto3" json:"fields,omitempty"`
// explain each result (added to the each row)
Explain bool `protobuf:"varint,9,opt,name=explain,proto3" json:"explain,omitempty"`
IsDeleted bool `protobuf:"varint,10,opt,name=is_deleted,json=isDeleted,proto3" json:"is_deleted,omitempty"`
Page int64 `protobuf:"varint,11,opt,name=page,proto3" json:"page,omitempty"`
Permission int64 `protobuf:"varint,12,opt,name=permission,proto3" json:"permission,omitempty"`
Explain bool `protobuf:"varint,9,opt,name=explain,proto3" json:"explain,omitempty"`
IsDeleted bool `protobuf:"varint,10,opt,name=is_deleted,json=isDeleted,proto3" json:"is_deleted,omitempty"`
Page int64 `protobuf:"varint,11,opt,name=page,proto3" json:"page,omitempty"`
Permission int64 `protobuf:"varint,12,opt,name=permission,proto3" json:"permission,omitempty"`
// Optionally specify which fields are included in the query
QueryFields []*ResourceSearchRequest_QueryField `protobuf:"bytes,13,rep,name=query_fields,json=queryFields,proto3" json:"query_fields,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -287,6 +348,13 @@ func (x *ResourceSearchRequest) GetPermission() int64 {
return 0
}
func (x *ResourceSearchRequest) GetQueryFields() []*ResourceSearchRequest_QueryField {
if x != nil {
return x.QueryFields
}
return nil
}
type ResourceSearchResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Error details
@@ -670,6 +738,70 @@ func (x *ResourceSearchRequest_Facet) GetLimit() int64 {
return 0
}
// Defines the field in the index to query
// Boost is optional, and allows weighting the field higher in the results
type ResourceSearchRequest_QueryField struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The field name in the index to query
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Type QueryFieldType `protobuf:"varint,2,opt,name=type,proto3,enum=resource.QueryFieldType" json:"type,omitempty"`
// Boost value for this field
Boost float32 `protobuf:"fixed32,3,opt,name=boost,proto3" json:"boost,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ResourceSearchRequest_QueryField) Reset() {
*x = ResourceSearchRequest_QueryField{}
mi := &file_search_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ResourceSearchRequest_QueryField) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ResourceSearchRequest_QueryField) ProtoMessage() {}
func (x *ResourceSearchRequest_QueryField) ProtoReflect() protoreflect.Message {
mi := &file_search_proto_msgTypes[9]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ResourceSearchRequest_QueryField.ProtoReflect.Descriptor instead.
func (*ResourceSearchRequest_QueryField) Descriptor() ([]byte, []int) {
return file_search_proto_rawDescGZIP(), []int{2, 2}
}
func (x *ResourceSearchRequest_QueryField) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *ResourceSearchRequest_QueryField) GetType() QueryFieldType {
if x != nil {
return x.Type
}
return QueryFieldType_DEFAULT
}
func (x *ResourceSearchRequest_QueryField) GetBoost() float32 {
if x != nil {
return x.Boost
}
return 0
}
type ResourceSearchResponse_Facet struct {
state protoimpl.MessageState `protogen:"open.v1"`
Field string `protobuf:"bytes,1,opt,name=field,proto3" json:"field,omitempty"`
@@ -685,7 +817,7 @@ type ResourceSearchResponse_Facet struct {
func (x *ResourceSearchResponse_Facet) Reset() {
*x = ResourceSearchResponse_Facet{}
mi := &file_search_proto_msgTypes[10]
mi := &file_search_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -697,7 +829,7 @@ func (x *ResourceSearchResponse_Facet) String() string {
func (*ResourceSearchResponse_Facet) ProtoMessage() {}
func (x *ResourceSearchResponse_Facet) ProtoReflect() protoreflect.Message {
mi := &file_search_proto_msgTypes[10]
mi := &file_search_proto_msgTypes[11]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -751,7 +883,7 @@ type ResourceSearchResponse_TermFacet struct {
func (x *ResourceSearchResponse_TermFacet) Reset() {
*x = ResourceSearchResponse_TermFacet{}
mi := &file_search_proto_msgTypes[11]
mi := &file_search_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -763,7 +895,7 @@ func (x *ResourceSearchResponse_TermFacet) String() string {
func (*ResourceSearchResponse_TermFacet) ProtoMessage() {}
func (x *ResourceSearchResponse_TermFacet) ProtoReflect() protoreflect.Message {
mi := &file_search_proto_msgTypes[11]
mi := &file_search_proto_msgTypes[12]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -818,7 +950,7 @@ var file_search_proto_rawDesc = string([]byte{
0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63,
0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e,
0x74, 0x22, 0x8e, 0x05, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65,
0x74, 0x22, 0xc3, 0x06, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65,
0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x07, 0x6f,
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x74, 0x69,
@@ -846,93 +978,109 @@ var file_search_proto_rawDesc = string([]byte{
0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x0b,
0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65,
0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a,
0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x30, 0x0a, 0x04, 0x53, 0x6f,
0x72, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63,
0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, 0x1a, 0x33, 0x0a, 0x05,
0x46, 0x61, 0x63, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c,
0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69,
0x74, 0x1a, 0x5f, 0x0a, 0x0a, 0x46, 0x61, 0x63, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12,
0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65,
0x79, 0x12, 0x3b, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x25, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f,
0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x4d, 0x0a, 0x0c, 0x71, 0x75,
0x65, 0x72, 0x79, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x2a, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
0x38, 0x01, 0x22, 0xea, 0x04, 0x0a, 0x16, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53,
0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a,
0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73,
0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65,
0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03,
0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x03,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e,
0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x07, 0x72,
0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f,
0x68, 0x69, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61,
0x6c, 0x48, 0x69, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x63,
0x6f, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x09, 0x71, 0x75, 0x65, 0x72, 0x79,
0x43, 0x6f, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x63, 0x6f, 0x72,
0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x53, 0x63, 0x6f, 0x72,
0x65, 0x12, 0x41, 0x0a, 0x05, 0x66, 0x61, 0x63, 0x65, 0x74, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x2b, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f,
0x74, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x0b, 0x71, 0x75,
0x65, 0x72, 0x79, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x1a, 0x30, 0x0a, 0x04, 0x53, 0x6f, 0x72,
0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18,
0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, 0x1a, 0x33, 0x0a, 0x05, 0x46,
0x61, 0x63, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69,
0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74,
0x1a, 0x64, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x12,
0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
0x6d, 0x65, 0x12, 0x2c, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e,
0x32, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x51, 0x75, 0x65, 0x72,
0x79, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65,
0x12, 0x14, 0x0a, 0x05, 0x62, 0x6f, 0x6f, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52,
0x05, 0x62, 0x6f, 0x6f, 0x73, 0x74, 0x1a, 0x5f, 0x0a, 0x0a, 0x46, 0x61, 0x63, 0x65, 0x74, 0x45,
0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x3b, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xea, 0x04, 0x0a, 0x16, 0x52, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x66,
0x61, 0x63, 0x65, 0x74, 0x1a, 0x8f, 0x01, 0x0a, 0x05, 0x46, 0x61, 0x63, 0x65, 0x74, 0x12, 0x14,
0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x66,
0x69, 0x65, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20,
0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x69,
0x73, 0x73, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6d, 0x69, 0x73,
0x73, 0x69, 0x6e, 0x67, 0x12, 0x40, 0x0a, 0x05, 0x74, 0x65, 0x72, 0x6d, 0x73, 0x18, 0x04, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x46, 0x61, 0x63, 0x65, 0x74, 0x52,
0x05, 0x74, 0x65, 0x72, 0x6d, 0x73, 0x1a, 0x35, 0x0a, 0x09, 0x54, 0x65, 0x72, 0x6d, 0x46, 0x61,
0x63, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x72, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x74, 0x65, 0x72, 0x6d, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74,
0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0x60, 0x0a,
0x0a, 0x46, 0x61, 0x63, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x3c, 0x0a,
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x72,
0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72,
0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12,
0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46,
0x61, 0x63, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22,
0x60, 0x0a, 0x15, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x65,
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65,
0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d,
0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x29, 0x0a, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x02,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e,
0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x04, 0x6b, 0x65, 0x79,
0x73, 0x22, 0x83, 0x01, 0x0a, 0x16, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x64,
0x65, 0x78, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0c,
0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01,
0x28, 0x03, 0x52, 0x0c, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74,
0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72,
0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74,
0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x32, 0xfe, 0x01, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x4b, 0x0a, 0x06, 0x53, 0x65, 0x61,
0x72, 0x63, 0x68, 0x12, 0x1f, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e,
0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61,
0x74, 0x73, 0x12, 0x1e, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x0e, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e,
0x64, 0x65, 0x78, 0x65, 0x73, 0x12, 0x1f, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x2e, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
0x65, 0x2e, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3b, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x68,
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x67,
0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61,
0x67, 0x65, 0x2f, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75,
0x72, 0x63, 0x65, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75,
0x6c, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62,
0x6c, 0x65, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74,
0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x68, 0x69, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52,
0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x48, 0x69, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x71, 0x75,
0x65, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x09,
0x71, 0x75, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78,
0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x6d, 0x61,
0x78, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x41, 0x0a, 0x05, 0x66, 0x61, 0x63, 0x65, 0x74, 0x18,
0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, 0x45, 0x6e, 0x74,
0x72, 0x79, 0x52, 0x05, 0x66, 0x61, 0x63, 0x65, 0x74, 0x1a, 0x8f, 0x01, 0x0a, 0x05, 0x46, 0x61,
0x63, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74,
0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12,
0x18, 0x0a, 0x07, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03,
0x52, 0x07, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x12, 0x40, 0x0a, 0x05, 0x74, 0x65, 0x72,
0x6d, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75,
0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72,
0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x46,
0x61, 0x63, 0x65, 0x74, 0x52, 0x05, 0x74, 0x65, 0x72, 0x6d, 0x73, 0x1a, 0x35, 0x0a, 0x09, 0x54,
0x65, 0x72, 0x6d, 0x46, 0x61, 0x63, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x72, 0x6d,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x72, 0x6d, 0x12, 0x14, 0x0a, 0x05,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x1a, 0x60, 0x0a, 0x0a, 0x46, 0x61, 0x63, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79,
0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b,
0x65, 0x79, 0x12, 0x3c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x26, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x3a, 0x02, 0x38, 0x01, 0x22, 0x60, 0x0a, 0x15, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49,
0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a,
0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x29, 0x0a, 0x04, 0x6b,
0x65, 0x79, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79,
0x52, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x22, 0x83, 0x01, 0x0a, 0x16, 0x52, 0x65, 0x62, 0x75, 0x69,
0x6c, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x22, 0x0a, 0x0c, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f, 0x75, 0x6e,
0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64,
0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12,
0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15,
0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52,
0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x2a, 0x40, 0x0a, 0x0e,
0x51, 0x75, 0x65, 0x72, 0x79, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b,
0x0a, 0x07, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x54,
0x45, 0x58, 0x54, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x4b, 0x45, 0x59, 0x57, 0x4f, 0x52, 0x44,
0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x48, 0x52, 0x41, 0x53, 0x45, 0x10, 0x03, 0x32, 0xfe,
0x01, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78,
0x12, 0x4b, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x1f, 0x2e, 0x72, 0x65, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65,
0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x72, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53,
0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a,
0x08, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x1e, 0x2e, 0x72, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x61,
0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x72, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x61,
0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x0e, 0x52, 0x65,
0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x12, 0x1f, 0x2e, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49,
0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e,
0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64,
0x49, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42,
0x3b, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72,
0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x6b,
0x67, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65,
0x64, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
})
var (
@@ -947,53 +1095,58 @@ func file_search_proto_rawDescGZIP() []byte {
return file_search_proto_rawDescData
}
var file_search_proto_msgTypes = make([]protoimpl.MessageInfo, 13)
var file_search_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_search_proto_msgTypes = make([]protoimpl.MessageInfo, 14)
var file_search_proto_goTypes = []any{
(*ResourceStatsRequest)(nil), // 0: resource.ResourceStatsRequest
(*ResourceStatsResponse)(nil), // 1: resource.ResourceStatsResponse
(*ResourceSearchRequest)(nil), // 2: resource.ResourceSearchRequest
(*ResourceSearchResponse)(nil), // 3: resource.ResourceSearchResponse
(*RebuildIndexesRequest)(nil), // 4: resource.RebuildIndexesRequest
(*RebuildIndexesResponse)(nil), // 5: resource.RebuildIndexesResponse
(*ResourceStatsResponse_Stats)(nil), // 6: resource.ResourceStatsResponse.Stats
(*ResourceSearchRequest_Sort)(nil), // 7: resource.ResourceSearchRequest.Sort
(*ResourceSearchRequest_Facet)(nil), // 8: resource.ResourceSearchRequest.Facet
nil, // 9: resource.ResourceSearchRequest.FacetEntry
(*ResourceSearchResponse_Facet)(nil), // 10: resource.ResourceSearchResponse.Facet
(*ResourceSearchResponse_TermFacet)(nil), // 11: resource.ResourceSearchResponse.TermFacet
nil, // 12: resource.ResourceSearchResponse.FacetEntry
(*ErrorResult)(nil), // 13: resource.ErrorResult
(*ListOptions)(nil), // 14: resource.ListOptions
(*ResourceKey)(nil), // 15: resource.ResourceKey
(*ResourceTable)(nil), // 16: resource.ResourceTable
(QueryFieldType)(0), // 0: resource.QueryFieldType
(*ResourceStatsRequest)(nil), // 1: resource.ResourceStatsRequest
(*ResourceStatsResponse)(nil), // 2: resource.ResourceStatsResponse
(*ResourceSearchRequest)(nil), // 3: resource.ResourceSearchRequest
(*ResourceSearchResponse)(nil), // 4: resource.ResourceSearchResponse
(*RebuildIndexesRequest)(nil), // 5: resource.RebuildIndexesRequest
(*RebuildIndexesResponse)(nil), // 6: resource.RebuildIndexesResponse
(*ResourceStatsResponse_Stats)(nil), // 7: resource.ResourceStatsResponse.Stats
(*ResourceSearchRequest_Sort)(nil), // 8: resource.ResourceSearchRequest.Sort
(*ResourceSearchRequest_Facet)(nil), // 9: resource.ResourceSearchRequest.Facet
(*ResourceSearchRequest_QueryField)(nil), // 10: resource.ResourceSearchRequest.QueryField
nil, // 11: resource.ResourceSearchRequest.FacetEntry
(*ResourceSearchResponse_Facet)(nil), // 12: resource.ResourceSearchResponse.Facet
(*ResourceSearchResponse_TermFacet)(nil), // 13: resource.ResourceSearchResponse.TermFacet
nil, // 14: resource.ResourceSearchResponse.FacetEntry
(*ErrorResult)(nil), // 15: resource.ErrorResult
(*ListOptions)(nil), // 16: resource.ListOptions
(*ResourceKey)(nil), // 17: resource.ResourceKey
(*ResourceTable)(nil), // 18: resource.ResourceTable
}
var file_search_proto_depIdxs = []int32{
13, // 0: resource.ResourceStatsResponse.error:type_name -> resource.ErrorResult
6, // 1: resource.ResourceStatsResponse.stats:type_name -> resource.ResourceStatsResponse.Stats
14, // 2: resource.ResourceSearchRequest.options:type_name -> resource.ListOptions
15, // 3: resource.ResourceSearchRequest.federated:type_name -> resource.ResourceKey
7, // 4: resource.ResourceSearchRequest.sortBy:type_name -> resource.ResourceSearchRequest.Sort
9, // 5: resource.ResourceSearchRequest.facet:type_name -> resource.ResourceSearchRequest.FacetEntry
13, // 6: resource.ResourceSearchResponse.error:type_name -> resource.ErrorResult
15, // 7: resource.ResourceSearchResponse.key:type_name -> resource.ResourceKey
16, // 8: resource.ResourceSearchResponse.results:type_name -> resource.ResourceTable
12, // 9: resource.ResourceSearchResponse.facet:type_name -> resource.ResourceSearchResponse.FacetEntry
15, // 10: resource.RebuildIndexesRequest.keys:type_name -> resource.ResourceKey
13, // 11: resource.RebuildIndexesResponse.error:type_name -> resource.ErrorResult
8, // 12: resource.ResourceSearchRequest.FacetEntry.value:type_name -> resource.ResourceSearchRequest.Facet
11, // 13: resource.ResourceSearchResponse.Facet.terms:type_name -> resource.ResourceSearchResponse.TermFacet
10, // 14: resource.ResourceSearchResponse.FacetEntry.value:type_name -> resource.ResourceSearchResponse.Facet
2, // 15: resource.ResourceIndex.Search:input_type -> resource.ResourceSearchRequest
0, // 16: resource.ResourceIndex.GetStats:input_type -> resource.ResourceStatsRequest
4, // 17: resource.ResourceIndex.RebuildIndexes:input_type -> resource.RebuildIndexesRequest
3, // 18: resource.ResourceIndex.Search:output_type -> resource.ResourceSearchResponse
1, // 19: resource.ResourceIndex.GetStats:output_type -> resource.ResourceStatsResponse
5, // 20: resource.ResourceIndex.RebuildIndexes:output_type -> resource.RebuildIndexesResponse
18, // [18:21] is the sub-list for method output_type
15, // [15:18] is the sub-list for method input_type
15, // [15:15] is the sub-list for extension type_name
15, // [15:15] is the sub-list for extension extendee
0, // [0:15] is the sub-list for field type_name
15, // 0: resource.ResourceStatsResponse.error:type_name -> resource.ErrorResult
7, // 1: resource.ResourceStatsResponse.stats:type_name -> resource.ResourceStatsResponse.Stats
16, // 2: resource.ResourceSearchRequest.options:type_name -> resource.ListOptions
17, // 3: resource.ResourceSearchRequest.federated:type_name -> resource.ResourceKey
8, // 4: resource.ResourceSearchRequest.sortBy:type_name -> resource.ResourceSearchRequest.Sort
11, // 5: resource.ResourceSearchRequest.facet:type_name -> resource.ResourceSearchRequest.FacetEntry
10, // 6: resource.ResourceSearchRequest.query_fields:type_name -> resource.ResourceSearchRequest.QueryField
15, // 7: resource.ResourceSearchResponse.error:type_name -> resource.ErrorResult
17, // 8: resource.ResourceSearchResponse.key:type_name -> resource.ResourceKey
18, // 9: resource.ResourceSearchResponse.results:type_name -> resource.ResourceTable
14, // 10: resource.ResourceSearchResponse.facet:type_name -> resource.ResourceSearchResponse.FacetEntry
17, // 11: resource.RebuildIndexesRequest.keys:type_name -> resource.ResourceKey
15, // 12: resource.RebuildIndexesResponse.error:type_name -> resource.ErrorResult
0, // 13: resource.ResourceSearchRequest.QueryField.type:type_name -> resource.QueryFieldType
9, // 14: resource.ResourceSearchRequest.FacetEntry.value:type_name -> resource.ResourceSearchRequest.Facet
13, // 15: resource.ResourceSearchResponse.Facet.terms:type_name -> resource.ResourceSearchResponse.TermFacet
12, // 16: resource.ResourceSearchResponse.FacetEntry.value:type_name -> resource.ResourceSearchResponse.Facet
3, // 17: resource.ResourceIndex.Search:input_type -> resource.ResourceSearchRequest
1, // 18: resource.ResourceIndex.GetStats:input_type -> resource.ResourceStatsRequest
5, // 19: resource.ResourceIndex.RebuildIndexes:input_type -> resource.RebuildIndexesRequest
4, // 20: resource.ResourceIndex.Search:output_type -> resource.ResourceSearchResponse
2, // 21: resource.ResourceIndex.GetStats:output_type -> resource.ResourceStatsResponse
6, // 22: resource.ResourceIndex.RebuildIndexes:output_type -> resource.RebuildIndexesResponse
20, // [20:23] is the sub-list for method output_type
17, // [17:20] is the sub-list for method input_type
17, // [17:17] is the sub-list for extension type_name
17, // [17:17] is the sub-list for extension extendee
0, // [0:17] is the sub-list for field type_name
}
func init() { file_search_proto_init() }
@@ -1007,13 +1160,14 @@ func file_search_proto_init() {
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_search_proto_rawDesc), len(file_search_proto_rawDesc)),
NumEnums: 0,
NumMessages: 13,
NumEnums: 1,
NumMessages: 14,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_search_proto_goTypes,
DependencyIndexes: file_search_proto_depIdxs,
EnumInfos: file_search_proto_enumTypes,
MessageInfos: file_search_proto_msgTypes,
}.Build()
File_search_proto = out.File

View File

@@ -31,9 +31,11 @@ const (
// Unlike the ResourceStore, this service can be exposed to clients directly
// It should be implemented with efficient indexes and does not need read-after-write semantics
type ResourceIndexClient interface {
// Query for documents
Search(ctx context.Context, in *ResourceSearchRequest, opts ...grpc.CallOption) (*ResourceSearchResponse, error)
// Get the resource stats
GetStats(ctx context.Context, in *ResourceStatsRequest, opts ...grpc.CallOption) (*ResourceStatsResponse, error)
// Rebuild the search index
RebuildIndexes(ctx context.Context, in *RebuildIndexesRequest, opts ...grpc.CallOption) (*RebuildIndexesResponse, error)
}
@@ -82,9 +84,11 @@ func (c *resourceIndexClient) RebuildIndexes(ctx context.Context, in *RebuildInd
// Unlike the ResourceStore, this service can be exposed to clients directly
// It should be implemented with efficient indexes and does not need read-after-write semantics
type ResourceIndexServer interface {
// Query for documents
Search(context.Context, *ResourceSearchRequest) (*ResourceSearchResponse, error)
// Get the resource stats
GetStats(context.Context, *ResourceStatsRequest) (*ResourceStatsResponse, error)
// Rebuild the search index
RebuildIndexes(context.Context, *RebuildIndexesRequest) (*RebuildIndexesResponse, error)
}

View File

@@ -81,6 +81,11 @@ type BleveOptions struct {
// Indexes that are not owned by current instance are eligible for cleanup.
// If nil, all indexes are owned by the current instance.
OwnsIndex func(key resource.NamespacedResource) (bool, error)
// ScoringModel defines the scoring model used for the bleve indexes
// Default: index.TFIDFScoring
// Supported values: index.TFIDFScoring and index.BM25Scoring
ScoringModel string
}
type bleveBackend struct {
@@ -368,7 +373,7 @@ func (b *bleveBackend) BuildIndex(
attribute.String("reason", indexBuildReason),
)
mapper, err := GetBleveMappings(fields)
mapper, err := GetBleveMappings(b.opts.ScoringModel, fields)
if err != nil {
return nil, err
}
@@ -1177,6 +1182,7 @@ func (b *bleveIndex) getIndex(
return b.index, nil
}
// nolint:gocyclo
func (b *bleveIndex) toBleveSearchRequest(ctx context.Context, req *resourcepb.ResourceSearchRequest, access authlib.AccessClient) (*bleve.SearchRequest, *resourcepb.ErrorResult) {
ctx, span := tracer.Start(ctx, "search.bleveIndex.toBleveSearchRequest")
defer span.End()
@@ -1235,42 +1241,62 @@ func (b *bleveIndex) toBleveSearchRequest(ctx context.Context, req *resourcepb.R
}
}
if len(req.Query) > 1 && strings.Contains(req.Query, "*") {
// wildcard query is expensive - should be used with caution
wildcard := bleve.NewWildcardQuery(req.Query)
queries = append(queries, wildcard)
}
if len(req.Query) > 1 {
if strings.Contains(req.Query, "*") {
// wildcard query is expensive - should be used with caution
wildcard := bleve.NewWildcardQuery(req.Query)
queries = append(queries, wildcard)
} else {
// When using a
searchrequest.Fields = append(searchrequest.Fields, resource.SEARCH_FIELD_SCORE)
disjoin := bleve.NewDisjunctionQuery()
queries = append(queries, disjoin)
if req.Query != "" && !strings.Contains(req.Query, "*") {
// Add a text query
searchrequest.Fields = append(searchrequest.Fields, resource.SEARCH_FIELD_SCORE)
queryFields := req.QueryFields
if len(queryFields) == 0 {
queryFields = []*resourcepb.ResourceSearchRequest_QueryField{
{
Name: resource.SEARCH_FIELD_TITLE,
Type: resourcepb.QueryFieldType_KEYWORD,
Boost: 10, // exact match -- includes ngrams! If they lived on their own field, we could score them differently
}, {
Name: resource.SEARCH_FIELD_TITLE,
Type: resourcepb.QueryFieldType_TEXT,
Boost: 2, // standard analyzer (with ngrams!)
}, {
Name: resource.SEARCH_FIELD_TITLE_PHRASE,
Type: resourcepb.QueryFieldType_TEXT,
Boost: 5, // standard analyzer
},
}
}
// There are multiple ways to match the query string to documents. The following queries are ordered by priority:
for _, field := range queryFields {
switch field.Type {
case resourcepb.QueryFieldType_TEXT, resourcepb.QueryFieldType_DEFAULT:
q := bleve.NewMatchQuery(removeSmallTerms(req.Query)) // removeSmallTerms should be part of the analyzer
q.SetBoost(float64(field.Boost))
q.SetField(field.Name)
q.Analyzer = standard.Name // analyze the text
q.Operator = query.MatchQueryOperatorAnd // all terms must match
disjoin.AddQuery(q)
// Query 1: Match the exact query string
queryExact := bleve.NewMatchQuery(req.Query)
queryExact.SetBoost(10.0)
queryExact.SetField(resource.SEARCH_FIELD_TITLE)
queryExact.Analyzer = keyword.Name // don't analyze the query input - treat it as a single token
queryExact.Operator = query.MatchQueryOperatorAnd // This doesn't make a difference for keyword analyzer, we add it just to be explicit.
searchQuery := bleve.NewDisjunctionQuery(queryExact)
case resourcepb.QueryFieldType_KEYWORD:
q := bleve.NewMatchQuery(req.Query)
q.SetBoost(float64(field.Boost))
q.SetField(field.Name)
q.Analyzer = keyword.Name // don't analyze the query input - treat it as a single token
disjoin.AddQuery(q)
// Query 2: Phrase query with standard analyzer
queryPhrase := bleve.NewMatchPhraseQuery(req.Query)
queryPhrase.SetBoost(5.0)
queryPhrase.SetField(resource.SEARCH_FIELD_TITLE)
queryPhrase.Analyzer = standard.Name
searchQuery.AddQuery(queryPhrase)
// Query 3: Match query with standard analyzer
queryAnalyzed := bleve.NewMatchQuery(removeSmallTerms(req.Query))
queryAnalyzed.SetField(resource.SEARCH_FIELD_TITLE)
queryAnalyzed.SetBoost(2.0)
queryAnalyzed.Analyzer = standard.Name
queryAnalyzed.Operator = query.MatchQueryOperatorAnd // Make sure all terms from the query are matched
searchQuery.AddQuery(queryAnalyzed)
queries = append(queries, searchQuery)
case resourcepb.QueryFieldType_PHRASE:
q := bleve.NewMatchPhraseQuery(req.Query)
q.SetBoost(float64(field.Boost))
q.SetField(field.Name)
q.Analyzer = standard.Name
disjoin.AddQuery(q)
}
}
}
}
switch len(queries) {
@@ -1872,7 +1898,7 @@ func (q *permissionScopedQuery) Searcher(ctx context.Context, i index.IndexReade
if err != nil {
return nil, err
}
filteringSearcher := bleveSearch.NewFilteringSearcher(ctx, searcher, func(d *search.DocumentMatch) bool {
filteringSearcher := bleveSearch.NewFilteringSearcher(ctx, searcher, func(_ *search.SearchContext, d *search.DocumentMatch) bool {
// The doc ID has the format: <namespace>/<group>/<resourceType>/<name>
// IndexInternalID will be the same as the doc ID when using an in-memory index, but when using a file-based
// index it becomes a binary encoded number that has some other internal meaning. Using ExternalID() will get the

View File

@@ -4,6 +4,7 @@ import (
"context"
"testing"
index "github.com/blevesearch/bleve_index_api"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/storage/unified/resource"
@@ -19,6 +20,7 @@ func TestBleveSearchBackend(t *testing.T) {
backend, err := NewBleveBackend(BleveOptions{
Root: tempDir,
FileThreshold: 5,
ScoringModel: index.BM25Scoring,
}, nil)
require.NoError(t, err)
require.NotNil(t, backend)
@@ -52,3 +54,32 @@ func TestSearchBackendBenchmark(t *testing.T) {
unitest.BenchmarkSearchBackend(t, backend, opts)
}
func BenchmarkScoringModels(b *testing.B) {
models := []string{index.TFIDFScoring, index.BM25Scoring}
for _, model := range models {
b.Run(model, func(b *testing.B) {
tempDir := b.TempDir()
backend, err := NewBleveBackend(BleveOptions{
Root: tempDir,
ScoringModel: model,
}, nil)
require.NoError(b, err)
require.NotNil(b, backend)
b.Cleanup(backend.Stop)
opts := &unitest.BenchmarkOptions{
NumResources: 1000,
Concurrency: 4,
NumNamespaces: 10,
NumGroups: 10,
NumResourceTypes: 10,
}
unitest.BenchmarkSearchBackend(b, backend, opts)
})
}
}

View File

@@ -5,13 +5,15 @@ import (
"github.com/blevesearch/bleve/v2/analysis/analyzer/keyword"
"github.com/blevesearch/bleve/v2/analysis/analyzer/standard"
"github.com/blevesearch/bleve/v2/mapping"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
func GetBleveMappings(fields resource.SearchableDocumentFields) (mapping.IndexMapping, error) {
func GetBleveMappings(scoringModel string, fields resource.SearchableDocumentFields) (mapping.IndexMapping, error) {
mapper := bleve.NewIndexMapping()
if scoringModel != "" {
mapper.ScoringModel = scoringModel
}
err := RegisterCustomAnalyzers(mapper)
if err != nil {

View File

@@ -13,7 +13,7 @@ import (
)
func TestDocumentMapping(t *testing.T) {
mappings, err := search.GetBleveMappings(nil)
mappings, err := search.GetBleveMappings("", nil)
require.NoError(t, err)
data := resource.IndexableDocument{
Title: "title",

View File

@@ -7,6 +7,7 @@ import (
"testing"
"github.com/blevesearch/bleve/v2"
index "github.com/blevesearch/bleve_index_api"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
@@ -258,6 +259,7 @@ func newTestDashboardsIndex(t testing.TB, threshold int64, size int64, writer re
backend, err := search.NewBleveBackend(search.BleveOptions{
Root: t.TempDir(),
FileThreshold: threshold, // use in-memory for tests
ScoringModel: index.BM25Scoring,
}, nil)
require.NoError(t, err)

View File

@@ -14,6 +14,7 @@ import (
"time"
"github.com/blevesearch/bleve/v2"
index "github.com/blevesearch/bleve_index_api"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/testutil"
"github.com/stretchr/testify/assert"
@@ -50,6 +51,7 @@ func TestBleveBackend(t *testing.T) {
backend, err := NewBleveBackend(BleveOptions{
Root: tmpdir,
FileThreshold: 5, // with more than 5 items we create a file on disk
ScoringModel: index.BM25Scoring,
}, nil)
require.NoError(t, err)
t.Cleanup(backend.Stop)
@@ -773,6 +775,7 @@ func setupBleveBackend(t *testing.T, options ...setupOption) (*bleveBackend, pro
IndexCacheTTL: defaultIndexCacheTTL,
Logger: log.NewNopLogger(),
BuildVersion: buildVersion,
ScoringModel: index.BM25Scoring,
}
for _, opt := range options {
opt(&opts)

View File

@@ -46,6 +46,7 @@ func NewSearchOptions(
BuildVersion: cfg.BuildVersion,
OwnsIndex: ownsIndexFn,
IndexMinUpdateInterval: cfg.IndexMinUpdateInterval,
ScoringModel: cfg.IndexScoringModel,
}, indexMetrics)
if err != nil {

View File

@@ -6,6 +6,7 @@ import (
"testing"
"time"
index "github.com/blevesearch/bleve_index_api"
"github.com/go-jose/go-jose/v4/jwt"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/require"
@@ -129,21 +130,28 @@ func TestIntegrationSearchAndStorage(t *testing.T) {
ctx := context.Background()
// Create a new bleve backend
search, err := search.NewBleveBackend(search.BleveOptions{
FileThreshold: 0,
Root: t.TempDir(),
}, nil)
require.NoError(t, err)
require.NotNil(t, search)
t.Cleanup(search.Stop)
scoringModels := []string{index.TFIDFScoring, index.BM25Scoring}
// Create a new resource backend
storage, _ := newTestBackend(t, false, 0)
require.NotNil(t, storage)
for _, model := range scoringModels {
t.Run(model, func(t *testing.T) {
// Create a new bleve backend
search, err := search.NewBleveBackend(search.BleveOptions{
FileThreshold: 0,
Root: t.TempDir(),
ScoringModel: model,
}, nil)
require.NoError(t, err)
require.NotNil(t, search)
t.Cleanup(search.Stop)
// Run the shared storage and search tests
unitest.RunTestSearchAndStorage(t, ctx, storage, search)
// Create a new resource backend
storage, _ := newTestBackend(t, false, 0)
require.NotNil(t, storage)
// Run the shared storage and search tests
unitest.RunTestSearchAndStorage(t, ctx, storage, search)
})
}
}
func TestClientServer(t *testing.T) {

View File

@@ -24,6 +24,24 @@ func TestMain(m *testing.M) {
testsuite.Run(m)
}
// mockElasticsearchHandler returns a handler that mocks Elasticsearch endpoints.
// It responds to GET / with cluster info (required for datasource initialization)
// and returns 401 Unauthorized for all other requests.
func mockElasticsearchHandler(onRequest func(r *http.Request)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == http.MethodGet && r.URL.Path == "/":
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"version":{"build_flavor":"default","number":"8.0.0"}}`))
default:
if onRequest != nil {
onRequest(r)
}
w.WriteHeader(http.StatusUnauthorized)
}
}
}
func TestIntegrationElasticsearch(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
@@ -35,9 +53,8 @@ func TestIntegrationElasticsearch(t *testing.T) {
ctx := context.Background()
var outgoingRequest *http.Request
outgoingServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
outgoingServer := httptest.NewServer(mockElasticsearchHandler(func(r *http.Request) {
outgoingRequest = r
w.WriteHeader(http.StatusUnauthorized)
}))
t.Cleanup(outgoingServer.Close)

View File

@@ -209,7 +209,7 @@
"path": "public/plugins/grafana-azure-monitor-datasource/img/azure_monitor_cpu.png"
}
],
"version": "12.3.0-pre",
"version": "12.4.0-pre",
"updated": "",
"keywords": [
"azure",
@@ -589,7 +589,7 @@
"hasUpdate": false,
"defaultNavUrl": "/plugins/datagrid/",
"category": "",
"state": "beta",
"state": "deprecated",
"signature": "internal",
"signatureType": "",
"signatureOrg": "",
@@ -639,7 +639,7 @@
]
},
"dependencies": {
"grafanaDependency": "",
"grafanaDependency": "\u003e=11.6.0",
"grafanaVersion": "*",
"plugins": [],
"extensions": {
@@ -880,7 +880,7 @@
},
"build": {},
"screenshots": null,
"version": "12.3.0-pre",
"version": "12.4.0-pre",
"updated": "",
"keywords": null
},
@@ -934,7 +934,7 @@
},
"build": {},
"screenshots": null,
"version": "12.3.0-pre",
"version": "12.4.0-pre",
"updated": "",
"keywords": [
"grafana",
@@ -1000,7 +1000,7 @@
},
"build": {},
"screenshots": null,
"version": "12.3.0-pre",
"version": "12.4.0-pre",
"updated": "",
"keywords": null
},
@@ -1217,7 +1217,7 @@
},
"build": {},
"screenshots": null,
"version": "12.3.0-pre",
"version": "12.4.0-pre",
"updated": "",
"keywords": null
},
@@ -1325,7 +1325,7 @@
},
"build": {},
"screenshots": null,
"version": "12.3.0-pre",
"version": "12.4.0-pre",
"updated": "",
"keywords": null
},
@@ -1375,7 +1375,7 @@
},
"build": {},
"screenshots": null,
"version": "12.3.0-pre",
"version": "12.4.0-pre",
"updated": "",
"keywords": null
},
@@ -1425,7 +1425,7 @@
},
"build": {},
"screenshots": null,
"version": "12.3.0-pre",
"version": "12.4.0-pre",
"updated": "",
"keywords": null
},
@@ -1575,7 +1575,7 @@
},
"build": {},
"screenshots": null,
"version": "",
"version": "12.4.0-pre",
"updated": "",
"keywords": null
},
@@ -1629,7 +1629,7 @@
},
"build": {},
"screenshots": null,
"version": "12.3.0-pre",
"version": "12.4.0-pre",
"updated": "",
"keywords": [
"grafana",
@@ -1734,7 +1734,7 @@
},
"build": {},
"screenshots": null,
"version": "12.3.0-pre",
"version": "12.4.0-pre",
"updated": "",
"keywords": null
},
@@ -2042,7 +2042,7 @@
},
"build": {},
"screenshots": null,
"version": "12.3.0-pre",
"version": "12.4.0-pre",
"updated": "",
"keywords": null
},
@@ -2092,7 +2092,7 @@
},
"build": {},
"screenshots": null,
"version": "12.3.0-pre",
"version": "12.4.0-pre",
"updated": "",
"keywords": null
},
@@ -2445,7 +2445,7 @@
},
"build": {},
"screenshots": null,
"version": "12.3.0-pre",
"version": "12.4.0-pre",
"updated": "",
"keywords": null
},

View File

@@ -97,7 +97,7 @@ func TestIntegrationSearchDevDashboards(t *testing.T) {
require.Equal(t, 16, fileCount, "file count from %s", devenv)
// Helper to call search
callSearch := func(user apis.User, params string) dashboardV0.SearchResults {
callSearch := func(user apis.User, params map[string]string) dashboardV0.SearchResults {
require.NotNil(t, user)
ns := user.Identity.GetNamespace()
cfg := dynamic.ConfigFor(user.NewRestConfig())
@@ -107,17 +107,12 @@ func TestIntegrationSearchDevDashboards(t *testing.T) {
var statusCode int
req := restClient.Get().AbsPath("apis", "dashboard.grafana.app", "v0alpha1", "namespaces", ns, "search").
//Param("explain", "true") // helpful to understand which field made things match
Param("limit", "1000").
Param("type", "dashboard") // Only search dashboards
for kv := range strings.SplitSeq(params, "&") {
if kv == "" {
continue
}
parts := strings.SplitN(kv, "=", 2)
if len(parts) == 2 {
req = req.Param(parts[0], parts[1])
}
for k, v := range params {
req = req.Param(k, v)
}
res := req.Do(ctx).StatusCode(&statusCode)
require.NoError(t, res.Error())
@@ -140,22 +135,47 @@ func TestIntegrationSearchDevDashboards(t *testing.T) {
testCases := []struct {
name string
user apis.User
params string
params map[string]string
}{
{
name: "all",
user: helper.Org1.Admin,
params: "", // only dashboards
name: "all",
user: helper.Org1.Admin,
},
{
name: "simple-query",
user: helper.Org1.Admin,
params: "query=stacking",
name: "query-single-word",
user: helper.Org1.Admin,
params: map[string]string{
"query": "stacking",
},
},
{
name: "with-text-panel",
user: helper.Org1.Admin,
params: "field=panel_types&panelType=text",
name: "query-multiple-words",
user: helper.Org1.Admin,
params: map[string]string{
"query": "graph softMin", // must match ALL terms
},
},
{
name: "with-text-panel",
user: helper.Org1.Admin,
params: map[string]string{
"field": "panel_types", // return panel types
"panelType": "text",
},
},
{
name: "title-ngram-prefix",
user: helper.Org1.Admin,
params: map[string]string{
"query": "zer", // should match "Zero Decimals Y Ticks"
},
},
{
name: "title-ngram-middle-word",
user: helper.Org1.Admin,
params: map[string]string{
"query": "decim", // should match "Zero Decimals Y Ticks"
},
},
}
for i, tc := range testCases {

View File

@@ -10,7 +10,7 @@
"panel-tests",
"graph-ng"
],
"score": 0.658
"score": 0.284
},
{
"resource": "dashboards",
@@ -21,8 +21,8 @@
"panel-tests",
"graph-ng"
],
"score": 0.625
"score": 0.269
}
],
"maxScore": 0.658
"maxScore": 0.284
}

View File

@@ -0,0 +1,17 @@
{
"totalHits": 1,
"hits": [
{
"resource": "dashboards",
"name": "timeseries-soft-limits",
"title": "Panel Tests - Graph NG - softMin/softMax",
"tags": [
"gdev",
"panel-tests",
"graph-ng"
],
"score": 0.024
}
],
"maxScore": 0.024
}

View File

@@ -0,0 +1,17 @@
{
"totalHits": 1,
"hits": [
{
"resource": "dashboards",
"name": "timeseries-y-ticks-zero-decimals",
"title": "Zero Decimals Y Ticks",
"tags": [
"gdev",
"panel-tests",
"graph-ng"
],
"score": 0.35
}
],
"maxScore": 0.35
}

View File

@@ -0,0 +1,17 @@
{
"totalHits": 1,
"hits": [
{
"resource": "dashboards",
"name": "timeseries-y-ticks-zero-decimals",
"title": "Zero Decimals Y Ticks",
"tags": [
"gdev",
"panel-tests",
"graph-ng"
],
"score": 0.35
}
],
"maxScore": 0.35
}

View File

@@ -3912,6 +3912,13 @@
"value"
],
"properties": {
"properties": {
"description": "Additional properties for multi-props variables",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"selected": {
"description": "Whether the option is selected or not",
"type": "boolean"

View File

@@ -3939,6 +3939,13 @@
"value"
],
"properties": {
"properties": {
"description": "Additional properties for multi-props variables",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"selected": {
"description": "Whether the option is selected or not",
"type": "boolean"

View File

@@ -92,7 +92,7 @@ func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthReque
}, nil
}
url := fmt.Sprintf("%v/v3/projects/%v/metricDescriptors", dsInfo.services[cloudMonitor].url, defaultProject)
url := fmt.Sprintf("%s/v3/projects/%s/metricDescriptors", dsInfo.services[cloudMonitor].url, defaultProject)
request, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
@@ -139,6 +139,7 @@ type datasourceInfo struct {
defaultProject string
clientEmail string
tokenUri string
universeDomain string
services map[string]datasourceService
privateKey string
usingImpersonation bool
@@ -150,6 +151,7 @@ type datasourceJSONData struct {
DefaultProject string `json:"defaultProject"`
ClientEmail string `json:"clientEmail"`
TokenURI string `json:"tokenUri"`
UniverseDomain string `json:"universeDomain"`
UsingImpersonation bool `json:"usingImpersonation"`
ServiceAccountToImpersonate string `json:"serviceAccountToImpersonate"`
}
@@ -179,6 +181,7 @@ func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.Inst
defaultProject: jsonData.DefaultProject,
clientEmail: jsonData.ClientEmail,
tokenUri: jsonData.TokenURI,
universeDomain: jsonData.UniverseDomain,
usingImpersonation: jsonData.UsingImpersonation,
serviceAccountToImpersonate: jsonData.ServiceAccountToImpersonate,
services: map[string]datasourceService{},
@@ -194,13 +197,13 @@ func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.Inst
return nil, err
}
for name, info := range routes {
for name := range routes {
client, err := newHTTPClient(dsInfo, opts, &httpClientProvider, name)
if err != nil {
return nil, err
}
dsInfo.services[name] = datasourceService{
url: info.url,
url: buildURL(name, dsInfo.universeDomain),
client: client,
}
}

View File

@@ -23,12 +23,12 @@ type routeInfo struct {
var routes = map[string]routeInfo{
cloudMonitor: {
method: "GET",
url: "https://monitoring.googleapis.com",
url: "https://monitoring.",
scopes: []string{cloudMonitorScope},
},
resourceManager: {
method: "GET",
url: "https://cloudresourcemanager.googleapis.com",
url: "https://cloudresourcemanager.",
scopes: []string{resourceManagerScope},
},
}
@@ -68,6 +68,13 @@ func getMiddleware(model *datasourceInfo, routePath string) (httpclient.Middlewa
return tokenprovider.AuthMiddleware(provider), nil
}
func buildURL(route string, universeDomain string) string {
if universeDomain == "" {
universeDomain = "googleapis.com"
}
return routes[route].url + universeDomain
}
func newHTTPClient(model *datasourceInfo, opts httpclient.Options, clientProvider *httpclient.Provider, route string) (*http.Client, error) {
m, err := getMiddleware(model, route)
if err != nil {

View File

@@ -111,7 +111,7 @@ func Test_setRequestVariables(t *testing.T) {
im: &fakeInstance{
services: map[string]datasourceService{
cloudMonitor: {
url: routes[cloudMonitor].url,
url: buildURL(cloudMonitor, "googleapis.com"),
client: &http.Client{},
},
},

View File

@@ -3,8 +3,8 @@ package elasticsearch
import (
"regexp"
"github.com/grafana/grafana/pkg/components/simplejson"
es "github.com/grafana/grafana/pkg/tsdb/elasticsearch/client"
"github.com/grafana/grafana/pkg/tsdb/elasticsearch/simplejson"
)
// addDateHistogramAgg adds a date histogram aggregation to the aggregation builder

View File

@@ -16,7 +16,6 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana-plugin-sdk-go/backend/tracing"
"github.com/grafana/grafana/pkg/services/featuremgmt"
)
// Used in logging to mark a stage
@@ -35,6 +34,7 @@ type DatasourceInfo struct {
Interval string
MaxConcurrentShardRequests int64
IncludeFrozen bool
ClusterInfo ClusterInfo
}
type ConfiguredFields struct {
@@ -159,7 +159,7 @@ func (c *baseClientImpl) ExecuteMultisearch(r *MultiSearchRequest) (*MultiSearch
resSpan.End()
}()
improvedParsingEnabled := isFeatureEnabled(c.ctx, featuremgmt.FlagElasticsearchImprovedParsing)
improvedParsingEnabled := isFeatureEnabled(c.ctx, "elasticsearchImprovedParsing")
msr, err := c.parser.parseMultiSearchResponse(res.Body, improvedParsingEnabled)
if err != nil {
return nil, err
@@ -197,7 +197,11 @@ func (c *baseClientImpl) createMultiSearchRequests(searchRequests []*SearchReque
func (c *baseClientImpl) getMultiSearchQueryParameters() string {
var qs []string
qs = append(qs, fmt.Sprintf("max_concurrent_shard_requests=%d", c.ds.MaxConcurrentShardRequests))
// if the build flavor is not serverless, we can use the max concurrent shard requests
// this is because serverless clusters do not support max concurrent shard requests
if !c.ds.ClusterInfo.IsServerless() && c.ds.MaxConcurrentShardRequests > 0 {
qs = append(qs, fmt.Sprintf("max_concurrent_shard_requests=%d", c.ds.MaxConcurrentShardRequests))
}
if c.ds.IncludeFrozen {
qs = append(qs, "ignore_throttled=false")

View File

@@ -15,7 +15,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/tsdb/elasticsearch/simplejson"
)
func TestClient_ExecuteMultisearch(t *testing.T) {

View File

@@ -0,0 +1,51 @@
package es
import (
"encoding/json"
"fmt"
"net/http"
)
type VersionInfo struct {
BuildFlavor string `json:"build_flavor"`
}
// ClusterInfo represents Elasticsearch cluster information returned from the root endpoint.
// It is used to determine cluster capabilities and configuration like whether the cluster is serverless.
type ClusterInfo struct {
Version VersionInfo `json:"version"`
}
const (
BuildFlavorServerless = "serverless"
)
// GetClusterInfo fetches cluster information from the Elasticsearch root endpoint.
// It returns the cluster build flavor which is used to determine if the cluster is serverless.
func GetClusterInfo(httpCli *http.Client, url string) (clusterInfo ClusterInfo, err error) {
resp, err := httpCli.Get(url)
if err != nil {
return ClusterInfo{}, fmt.Errorf("error getting ES cluster info: %w", err)
}
if resp.StatusCode != http.StatusOK {
return ClusterInfo{}, fmt.Errorf("unexpected status code %d getting ES cluster info", resp.StatusCode)
}
defer func() {
if closeErr := resp.Body.Close(); closeErr != nil && err == nil {
err = fmt.Errorf("error closing response body: %w", closeErr)
}
}()
err = json.NewDecoder(resp.Body).Decode(&clusterInfo)
if err != nil {
return ClusterInfo{}, fmt.Errorf("error decoding ES cluster info: %w", err)
}
return clusterInfo, nil
}
func (ci ClusterInfo) IsServerless() bool {
return ci.Version.BuildFlavor == BuildFlavorServerless
}

View File

@@ -0,0 +1,188 @@
package es
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetClusterInfo(t *testing.T) {
t.Run("Should successfully get cluster info", func(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
_, err := rw.Write([]byte(`{
"name": "test-cluster",
"cluster_name": "elasticsearch",
"cluster_uuid": "abc123",
"version": {
"number": "8.0.0",
"build_flavor": "default",
"build_type": "tar",
"build_hash": "abc123",
"build_date": "2023-01-01T00:00:00.000Z",
"build_snapshot": false,
"lucene_version": "9.0.0"
}
}`))
require.NoError(t, err)
}))
t.Cleanup(func() {
ts.Close()
})
clusterInfo, err := GetClusterInfo(ts.Client(), ts.URL)
require.NoError(t, err)
require.NotNil(t, clusterInfo)
assert.Equal(t, "default", clusterInfo.Version.BuildFlavor)
})
t.Run("Should successfully get serverless cluster info", func(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
_, err := rw.Write([]byte(`{
"name": "serverless-cluster",
"cluster_name": "elasticsearch",
"cluster_uuid": "def456",
"version": {
"number": "8.11.0",
"build_flavor": "serverless",
"build_type": "docker",
"build_hash": "def456",
"build_date": "2023-11-01T00:00:00.000Z",
"build_snapshot": false,
"lucene_version": "9.8.0"
}
}`))
require.NoError(t, err)
}))
t.Cleanup(func() {
ts.Close()
})
clusterInfo, err := GetClusterInfo(ts.Client(), ts.URL)
require.NoError(t, err)
require.NotNil(t, clusterInfo)
assert.Equal(t, "serverless", clusterInfo.Version.BuildFlavor)
assert.True(t, clusterInfo.IsServerless())
})
t.Run("Should return error when HTTP request fails", func(t *testing.T) {
clusterInfo, err := GetClusterInfo(http.DefaultClient, "http://invalid-url-that-does-not-exist.local:9999")
require.Error(t, err)
require.Equal(t, ClusterInfo{}, clusterInfo)
assert.Contains(t, err.Error(), "error getting ES cluster info")
})
t.Run("Should return error when response body is invalid JSON", func(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
_, err := rw.Write([]byte(`{"invalid json`))
require.NoError(t, err)
}))
t.Cleanup(func() {
ts.Close()
})
clusterInfo, err := GetClusterInfo(ts.Client(), ts.URL)
require.Error(t, err)
require.Equal(t, ClusterInfo{}, clusterInfo)
assert.Contains(t, err.Error(), "error decoding ES cluster info")
})
t.Run("Should handle empty version object", func(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
_, err := rw.Write([]byte(`{
"name": "test-cluster",
"version": {}
}`))
require.NoError(t, err)
}))
t.Cleanup(func() {
ts.Close()
})
clusterInfo, err := GetClusterInfo(ts.Client(), ts.URL)
require.NoError(t, err)
require.Equal(t, ClusterInfo{}, clusterInfo)
assert.Equal(t, "", clusterInfo.Version.BuildFlavor)
assert.False(t, clusterInfo.IsServerless())
})
t.Run("Should handle HTTP error status codes", func(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(http.StatusUnauthorized)
_, err := rw.Write([]byte(`{"error": "Unauthorized"}`))
require.NoError(t, err)
}))
t.Cleanup(func() {
ts.Close()
})
clusterInfo, err := GetClusterInfo(ts.Client(), ts.URL)
require.Error(t, err)
require.Equal(t, ClusterInfo{}, clusterInfo)
assert.Contains(t, err.Error(), "unexpected status code 401 getting ES cluster info")
})
}
func TestClusterInfo_IsServerless(t *testing.T) {
t.Run("Should return true when build_flavor is serverless", func(t *testing.T) {
clusterInfo := ClusterInfo{
Version: VersionInfo{
BuildFlavor: BuildFlavorServerless,
},
}
assert.True(t, clusterInfo.IsServerless())
})
t.Run("Should return false when build_flavor is default", func(t *testing.T) {
clusterInfo := ClusterInfo{
Version: VersionInfo{
BuildFlavor: "default",
},
}
assert.False(t, clusterInfo.IsServerless())
})
t.Run("Should return false when build_flavor is empty", func(t *testing.T) {
clusterInfo := ClusterInfo{
Version: VersionInfo{
BuildFlavor: "",
},
}
assert.False(t, clusterInfo.IsServerless())
})
t.Run("Should return false when build_flavor is unknown value", func(t *testing.T) {
clusterInfo := ClusterInfo{
Version: VersionInfo{
BuildFlavor: "unknown",
},
}
assert.False(t, clusterInfo.IsServerless())
})
t.Run("should return false when cluster info is empty", func(t *testing.T) {
clusterInfo := ClusterInfo{}
assert.False(t, clusterInfo.IsServerless())
})
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/tsdb/elasticsearch/simplejson"
)
func TestSearchRequest(t *testing.T) {

View File

@@ -6,8 +6,8 @@ import (
"strconv"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/components/simplejson"
es "github.com/grafana/grafana/pkg/tsdb/elasticsearch/client"
"github.com/grafana/grafana/pkg/tsdb/elasticsearch/simplejson"
)
// processQuery processes a single query and adds it to the multi-search request builder

View File

@@ -3,7 +3,7 @@ package elasticsearch
import (
"strconv"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/tsdb/elasticsearch/simplejson"
)
// setFloatPath converts a string value at the specified path to float64

View File

@@ -88,6 +88,14 @@ func newInstanceSettings(httpClientProvider *httpclient.Provider) datasource.Ins
httpCliOpts.SigV4.Service = "es"
}
apiKeyAuth, ok := jsonData["apiKeyAuth"].(bool)
if ok && apiKeyAuth {
apiKey := settings.DecryptedSecureJSONData["apiKey"]
if apiKey != "" {
httpCliOpts.Header.Add("Authorization", "ApiKey "+apiKey)
}
}
httpCli, err := httpClientProvider.New(httpCliOpts)
if err != nil {
return nil, err
@@ -151,6 +159,11 @@ func newInstanceSettings(httpClientProvider *httpclient.Provider) datasource.Ins
includeFrozen = false
}
clusterInfo, err := es.GetClusterInfo(httpCli, settings.URL)
if err != nil {
return nil, err
}
configuredFields := es.ConfiguredFields{
TimeField: timeField,
LogLevelField: logLevelField,
@@ -166,6 +179,7 @@ func newInstanceSettings(httpClientProvider *httpclient.Provider) datasource.Ins
ConfiguredFields: configuredFields,
Interval: interval,
IncludeFrozen: includeFrozen,
ClusterInfo: clusterInfo,
}
return model, nil
}

Some files were not shown because too many files have changed in this diff Show More