diff --git a/.drone.yml b/.drone.yml index df536b9f81c..6883dcac6b6 100644 --- a/.drone.yml +++ b/.drone.yml @@ -4687,9 +4687,9 @@ steps: path: /github-app - commands: - export GITHUB_TOKEN=$(cat /github-app/token) - - 'dagger run --silent /src/grafana-build artifacts -a $${ARTIFACTS} --grafana-ref=$${GRAFANA_REF} - --enterprise-ref=$${ENTERPRISE_REF} --grafana-repo=$${GRAFANA_REPO} --version=$${VERSION} ' - - --go-version=1.23.5 + - dagger run --silent /src/grafana-build artifacts -a $${ARTIFACTS} --grafana-ref=$${GRAFANA_REF} + --enterprise-ref=$${ENTERPRISE_REF} --grafana-repo=$${GRAFANA_REPO} --version=$${VERSION} + --go-version=1.23.5 depends_on: - github-app-generate-token environment: @@ -4734,6 +4734,8 @@ steps: - printenv GCP_KEY_BASE64 | base64 -d > /tmp/key.json - gcloud auth activate-service-account --key-file=/tmp/key.json - gcloud storage cp -r dist/* $${UPLOAD_TO} + depends_on: + - rgm-build environment: _EXPERIMENTAL_DAGGER_CLOUD_TOKEN: from_secret: dagger_token @@ -4774,6 +4776,8 @@ volumes: name: docker - name: github-app path: /github-app +- name: github-app + temp: {} --- clone: retries: 3 @@ -5560,6 +5564,6 @@ kind: secret name: gcr_credentials --- kind: signature -hmac: 6e01278547a6f0803b7723e5f1e73bd94c572fa4805517232d3897717cee30f6 +hmac: 558d477c002eb799c23f6631aafc7df933518e445e59f34ceb989e73f4dc60bc ... diff --git a/.github/workflows/deploy-pr-preview.yml b/.github/workflows/deploy-pr-preview.yml index dcb8fcd1c2a..bfad92ebf73 100644 --- a/.github/workflows/deploy-pr-preview.yml +++ b/.github/workflows/deploy-pr-preview.yml @@ -11,14 +11,21 @@ on: jobs: deploy-pr-preview: - if: ${{ ! github.event.pull_request.head.repo.fork }} + if: "!github.event.pull_request.head.repo.fork" uses: grafana/writers-toolkit/.github/workflows/deploy-preview.yml@main with: - sha: ${{ github.event.pull_request.head.sha }} branch: ${{ github.head_ref }} event_number: ${{ github.event.number }} - title: ${{ github.event.pull_request.title }} repo: grafana - website_directory: content/docs/grafana/latest - relative_prefix: /docs/grafana/latest/ - index_file: true + sha: ${{ github.event.pull_request.head.sha }} + sources: | + [ + { + "index_file": "content/docs/grafana/_index.md", + "relative_prefix": "/docs/grafana/latest/", + "repo": "grafana", + "source_directory": "docs/sources", + "website_directory": "content/docs/grafana/latest" + } + ] + title: ${{ github.event.pull_request.title }} diff --git a/apps/advisor/pkg/app/authorizer.go b/apps/advisor/pkg/app/authorizer.go index 67defbc85d9..4d216ee9edd 100644 --- a/apps/advisor/pkg/app/authorizer.go +++ b/apps/advisor/pkg/app/authorizer.go @@ -22,7 +22,7 @@ func GetAuthorizer() authorizer.Authorizer { } // check if is admin - if u.GetIsGrafanaAdmin() { + if u.HasRole(identity.RoleAdmin) { return authorizer.DecisionAllow, "", nil } diff --git a/apps/advisor/pkg/app/authorizer_test.go b/apps/advisor/pkg/app/authorizer_test.go index 84414362fbd..1384176969e 100644 --- a/apps/advisor/pkg/app/authorizer_test.go +++ b/apps/advisor/pkg/app/authorizer_test.go @@ -75,4 +75,8 @@ func (m *mockUser) GetIsGrafanaAdmin() bool { return m.isGrafanaAdmin } +func (m *mockUser) HasRole(role identity.RoleType) bool { + return role == identity.RoleAdmin && m.isGrafanaAdmin +} + // Implement other methods of identity.Requester as needed diff --git a/apps/advisor/pkg/app/checkregistry/checkregistry.go b/apps/advisor/pkg/app/checkregistry/checkregistry.go index 1b690c70892..335b9cc7567 100644 --- a/apps/advisor/pkg/app/checkregistry/checkregistry.go +++ b/apps/advisor/pkg/app/checkregistry/checkregistry.go @@ -62,4 +62,5 @@ func (s *Service) Checks() []checks.Check { type AdvisorAppConfig struct { CheckRegistry CheckService PluginConfig map[string]string + StackID string } diff --git a/apps/advisor/pkg/app/checks/datasourcecheck/check.go b/apps/advisor/pkg/app/checks/datasourcecheck/check.go index 2cab8e3f903..0cdac81e4b8 100644 --- a/apps/advisor/pkg/app/checks/datasourcecheck/check.go +++ b/apps/advisor/pkg/app/checks/datasourcecheck/check.go @@ -2,17 +2,18 @@ package datasourcecheck import ( "context" + "errors" "fmt" "github.com/grafana/grafana-plugin-sdk-go/backend" advisor "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1" "github.com/grafana/grafana/apps/advisor/pkg/app/checks" "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" "github.com/grafana/grafana/pkg/util" - "k8s.io/klog/v2" ) type check struct { @@ -20,6 +21,7 @@ type check struct { PluginStore pluginstore.Store PluginContextProvider pluginContextProvider PluginClient plugins.Client + log log.Logger } func New( @@ -33,6 +35,7 @@ func New( PluginStore: pluginStore, PluginContextProvider: pluginContextProvider, PluginClient: pluginClient, + log: log.New("advisor.datasourcecheck"), } } @@ -58,6 +61,7 @@ func (c *check) Steps() []checks.Step { &healthCheckStep{ PluginContextProvider: c.PluginContextProvider, PluginClient: c.PluginClient, + log: c.log, }, } } @@ -102,6 +106,7 @@ func (s *uidValidationStep) Run(ctx context.Context, obj *advisor.CheckSpec, i a type healthCheckStep struct { PluginContextProvider pluginContextProvider PluginClient plugins.Client + log log.Logger } func (s *healthCheckStep) Title() string { @@ -134,7 +139,7 @@ func (s *healthCheckStep) Run(ctx context.Context, obj *advisor.CheckSpec, i any pCtx, err := s.PluginContextProvider.GetWithDataSource(ctx, ds.Type, requester, ds) if err != nil { // Unable to check health check - klog.Error("Failed to get plugin context", "datasource_uid", ds.UID, "error", err) + s.log.Error("Failed to get plugin context", "datasource_uid", ds.UID, "error", err) return nil, nil } req := &backend.CheckHealthRequest{ @@ -143,6 +148,15 @@ func (s *healthCheckStep) Run(ctx context.Context, obj *advisor.CheckSpec, i any } resp, err := s.PluginClient.CheckHealth(ctx, req) if err != nil || resp.Status != backend.HealthStatusOk { + if err != nil { + s.log.Debug("Failed to check health", "datasource_uid", ds.UID, "error", err) + if errors.Is(err, plugins.ErrMethodNotImplemented) || errors.Is(err, plugins.ErrPluginUnavailable) { + // The plugin does not support backend health checks + return nil, nil + } + } else { + s.log.Debug("Failed to check health", "datasource_uid", ds.UID, "status", resp.Status, "message", resp.Message) + } return checks.NewCheckReportFailure( advisor.CheckReportFailureSeverityHigh, s.ID(), diff --git a/apps/advisor/pkg/app/checks/datasourcecheck/check_test.go b/apps/advisor/pkg/app/checks/datasourcecheck/check_test.go index 813a6434ad9..ee8a991555d 100644 --- a/apps/advisor/pkg/app/checks/datasourcecheck/check_test.go +++ b/apps/advisor/pkg/app/checks/datasourcecheck/check_test.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" advisor "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/user" @@ -28,6 +29,7 @@ func TestCheck_Run(t *testing.T) { DatasourceSvc: mockDatasourceSvc, PluginContextProvider: mockPluginContextProvider, PluginClient: mockPluginClient, + log: log.New("advisor.datasourcecheck"), } ctx := identity.WithRequester(context.Background(), &user.SignedInUser{}) @@ -62,6 +64,7 @@ func TestCheck_Run(t *testing.T) { DatasourceSvc: mockDatasourceSvc, PluginContextProvider: mockPluginContextProvider, PluginClient: mockPluginClient, + log: log.New("advisor.datasourcecheck"), } ctx := identity.WithRequester(context.Background(), &user.SignedInUser{}) @@ -97,6 +100,7 @@ func TestCheck_Run(t *testing.T) { DatasourceSvc: mockDatasourceSvc, PluginContextProvider: mockPluginContextProvider, PluginClient: mockPluginClient, + log: log.New("advisor.datasourcecheck"), } ctx := identity.WithRequester(context.Background(), &user.SignedInUser{}) @@ -118,6 +122,40 @@ func TestCheck_Run(t *testing.T) { assert.Len(t, failures, 1) assert.Equal(t, "health-check", failures[0].StepID) }) + + t.Run("should skip health check when plugin does not support backend health checks", func(t *testing.T) { + datasources := []*datasources.DataSource{ + {UID: "valid-uid-1", Type: "prometheus", Name: "Prometheus"}, + } + mockDatasourceSvc := &MockDatasourceSvc{dss: datasources} + mockPluginContextProvider := &MockPluginContextProvider{pCtx: backend.PluginContext{}} + mockPluginClient := &MockPluginClient{err: plugins.ErrMethodNotImplemented} + + check := &check{ + DatasourceSvc: mockDatasourceSvc, + PluginContextProvider: mockPluginContextProvider, + PluginClient: mockPluginClient, + log: log.New("advisor.datasourcecheck"), + } + + ctx := identity.WithRequester(context.Background(), &user.SignedInUser{}) + items, err := check.Items(ctx) + assert.NoError(t, err) + failures := []advisor.CheckReportFailure{} + for _, step := range check.Steps() { + for _, item := range items { + stepFailures, err := step.Run(ctx, &advisor.CheckSpec{}, item) + assert.NoError(t, err) + if stepFailures != nil { + failures = append(failures, *stepFailures) + } + } + } + + assert.NoError(t, err) + assert.Equal(t, 1, len(items)) + assert.Len(t, failures, 0) + }) } type MockDatasourceSvc struct { @@ -142,8 +180,9 @@ type MockPluginClient struct { plugins.Client res *backend.CheckHealthResult + err error } func (m *MockPluginClient) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { - return m.res, nil + return m.res, m.err } diff --git a/apps/advisor/pkg/app/checks/utils.go b/apps/advisor/pkg/app/checks/utils.go index b9b609b10a4..145c98d0961 100644 --- a/apps/advisor/pkg/app/checks/utils.go +++ b/apps/advisor/pkg/app/checks/utils.go @@ -1,7 +1,12 @@ package checks import ( + "fmt" + "strconv" + + "github.com/grafana/authlib/types" advisor "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const ( @@ -22,3 +27,14 @@ func NewCheckReportFailure( Links: links, } } + +func GetNamespace(stackID string) (string, error) { + if stackID == "" { + return metav1.NamespaceDefault, nil + } + stackId, err := strconv.ParseInt(stackID, 10, 64) + if err != nil { + return "", fmt.Errorf("invalid stack id: %s", stackID) + } + return types.CloudNamespaceFormatter(stackId), nil +} diff --git a/apps/advisor/pkg/app/checks/utils_test.go b/apps/advisor/pkg/app/checks/utils_test.go new file mode 100644 index 00000000000..ad0fda89de8 --- /dev/null +++ b/apps/advisor/pkg/app/checks/utils_test.go @@ -0,0 +1,46 @@ +package checks + +import ( + "testing" + + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestGetNamespace(t *testing.T) { + tests := []struct { + name string + input string + expected string + expectedErr string + }{ + { + name: "empty stack ID", + input: "", + expected: metav1.NamespaceDefault, + }, + { + name: "valid stack ID", + input: "1234567890", + expected: "stacks-1234567890", + }, + { + name: "invalid stack ID", + input: "invalid", + expected: "", + expectedErr: "invalid stack id: invalid", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := GetNamespace(tt.input) + if tt.expectedErr != "" { + assert.EqualError(t, err, tt.expectedErr) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.expected, result) + }) + } +} diff --git a/apps/advisor/pkg/app/checkscheduler/checkscheduler.go b/apps/advisor/pkg/app/checkscheduler/checkscheduler.go index 0a2d2c4e0bf..2bcb8d43f19 100644 --- a/apps/advisor/pkg/app/checkscheduler/checkscheduler.go +++ b/apps/advisor/pkg/app/checkscheduler/checkscheduler.go @@ -29,6 +29,7 @@ type Runner struct { client resource.Client evaluationInterval time.Duration maxHistory int + namespace string } // NewRunner creates a new Runner. @@ -47,6 +48,10 @@ func New(cfg app.Config) (app.Runnable, error) { if err != nil { return nil, err } + namespace, err := checks.GetNamespace(specificConfig.StackID) + if err != nil { + return nil, err + } // Prepare storage client clientGenerator := k8s.NewClientRegistry(cfg.KubeConfig, k8s.ClientConfig{}) @@ -60,6 +65,7 @@ func New(cfg app.Config) (app.Runnable, error) { client: client, evaluationInterval: evalInterval, maxHistory: maxHistory, + namespace: namespace, }, nil } @@ -114,7 +120,7 @@ func (r *Runner) Run(ctx context.Context) error { // regardless of its ID. This assumes that the checks are created in batches // so a batch will have a similar creation time. func (r *Runner) checkLastCreated(ctx context.Context) (time.Time, error) { - list, err := r.client.List(ctx, metav1.NamespaceDefault, resource.ListOptions{}) + list, err := r.client.List(ctx, r.namespace, resource.ListOptions{}) if err != nil { return time.Time{}, err } @@ -134,7 +140,7 @@ func (r *Runner) createChecks(ctx context.Context) error { obj := &advisorv0alpha1.Check{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "check-", - Namespace: metav1.NamespaceDefault, + Namespace: r.namespace, Labels: map[string]string{ checks.TypeLabel: check.ID(), }, @@ -152,7 +158,7 @@ func (r *Runner) createChecks(ctx context.Context) error { // cleanupChecks deletes the olders checks if the number of checks exceeds the limit. func (r *Runner) cleanupChecks(ctx context.Context) error { - list, err := r.client.List(ctx, metav1.NamespaceDefault, resource.ListOptions{Limit: -1}) + list, err := r.client.List(ctx, r.namespace, resource.ListOptions{Limit: -1}) if err != nil { return err } diff --git a/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer.go b/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer.go index 50f2a09b6d2..2ed38e51ecb 100644 --- a/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer.go +++ b/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/grafana-app-sdk/resource" advisorv0alpha1 "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1" "github.com/grafana/grafana/apps/advisor/pkg/app/checkregistry" + "github.com/grafana/grafana/apps/advisor/pkg/app/checks" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -19,6 +20,7 @@ import ( type Runner struct { checkRegistry checkregistry.CheckService client resource.Client + namespace string } // NewRunner creates a new Runner. @@ -29,6 +31,10 @@ func New(cfg app.Config) (app.Runnable, error) { return nil, fmt.Errorf("invalid config type") } checkRegistry := specificConfig.CheckRegistry + namespace, err := checks.GetNamespace(specificConfig.StackID) + if err != nil { + return nil, err + } // Prepare storage client clientGenerator := k8s.NewClientRegistry(cfg.KubeConfig, k8s.ClientConfig{}) @@ -40,6 +46,7 @@ func New(cfg app.Config) (app.Runnable, error) { return &Runner{ checkRegistry: checkRegistry, client: client, + namespace: namespace, }, nil } @@ -58,7 +65,7 @@ func (r *Runner) Run(ctx context.Context) error { obj := &advisorv0alpha1.CheckType{ ObjectMeta: metav1.ObjectMeta{ Name: t.ID(), - Namespace: metav1.NamespaceDefault, + Namespace: r.namespace, }, Spec: advisorv0alpha1.CheckTypeSpec{ Name: t.ID(), diff --git a/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer_test.go b/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer_test.go index f1623a6534b..97fc7742504 100644 --- a/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer_test.go +++ b/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer_test.go @@ -3,6 +3,7 @@ package checktyperegisterer import ( "context" "errors" + "fmt" "testing" "github.com/grafana/grafana-app-sdk/resource" @@ -88,6 +89,24 @@ func TestCheckTypesRegisterer_Run(t *testing.T) { }, expectedErr: errors.New("update error"), }, + { + name: "custom namespace", + checks: []checks.Check{ + &mockCheck{ + id: "check1", + steps: []checks.Step{ + &mockStep{id: "step1", title: "Step 1", description: "Description 1"}, + }, + }, + }, + createFunc: func(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.CreateOptions) (resource.Object, error) { + if obj.GetNamespace() != "custom-namespace" { + return nil, fmt.Errorf("expected namespace %s, got %s", "custom-namespace", obj.GetNamespace()) + } + return obj, nil + }, + expectedErr: nil, + }, } for _, tt := range tests { @@ -98,6 +117,7 @@ func TestCheckTypesRegisterer_Run(t *testing.T) { createFunc: tt.createFunc, updateFunc: tt.updateFunc, }, + namespace: "custom-namespace", } err := r.Run(context.Background()) if err != nil { diff --git a/apps/alerting/notifications/go.mod b/apps/alerting/notifications/go.mod index ebbf0284681..74dc1dfe028 100644 --- a/apps/alerting/notifications/go.mod +++ b/apps/alerting/notifications/go.mod @@ -72,13 +72,13 @@ require ( go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.32.0 // indirect + golang.org/x/crypto v0.35.0 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/oauth2 v0.26.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sys v0.30.0 // indirect - golang.org/x/term v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/term v0.29.0 // indirect + golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.9.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect diff --git a/apps/alerting/notifications/go.sum b/apps/alerting/notifications/go.sum index 34db4a1a692..cae5be00879 100644 --- a/apps/alerting/notifications/go.sum +++ b/apps/alerting/notifications/go.sum @@ -207,8 +207,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -223,11 +223,11 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= -golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -239,12 +239,12 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/apps/investigations/go.mod b/apps/investigations/go.mod index 498b2e5eee7..12da166af27 100644 --- a/apps/investigations/go.mod +++ b/apps/investigations/go.mod @@ -62,12 +62,12 @@ require ( go.opentelemetry.io/otel/sdk v1.34.0 // indirect go.opentelemetry.io/otel/trace v1.34.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/oauth2 v0.26.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect - golang.org/x/term v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/term v0.29.0 // indirect + golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.9.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect diff --git a/apps/investigations/go.sum b/apps/investigations/go.sum index f6f6a0e3981..d3caa3d08e3 100644 --- a/apps/investigations/go.sum +++ b/apps/investigations/go.sum @@ -155,10 +155,10 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= -golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -169,12 +169,12 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/apps/playlist/go.mod b/apps/playlist/go.mod index ccc6715d0f5..cd07f8c199e 100644 --- a/apps/playlist/go.mod +++ b/apps/playlist/go.mod @@ -63,12 +63,12 @@ require ( go.opentelemetry.io/otel/sdk v1.34.0 // indirect go.opentelemetry.io/otel/trace v1.34.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/oauth2 v0.26.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect - golang.org/x/term v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/term v0.29.0 // indirect + golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.9.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect diff --git a/apps/playlist/go.sum b/apps/playlist/go.sum index f6f6a0e3981..d3caa3d08e3 100644 --- a/apps/playlist/go.sum +++ b/apps/playlist/go.sum @@ -155,10 +155,10 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= -golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -169,12 +169,12 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/docs/sources/alerting/alerting-rules/link-alert-rules-to-panels.md b/docs/sources/alerting/alerting-rules/link-alert-rules-to-panels.md index aeaddf1e405..746cb778052 100644 --- a/docs/sources/alerting/alerting-rules/link-alert-rules-to-panels.md +++ b/docs/sources/alerting/alerting-rules/link-alert-rules-to-panels.md @@ -44,6 +44,10 @@ Grafana allows you to link an alert rule to a dashboard panel. This can help you An alert rule is linked to a panel by setting the [`dashboardUId` and `panelId` annotations](ref:annotations). Both annotations must be set together. +{{% admonition type="tutorial" %}} +For a hands-on example of integrating alert rules with dashboards, check out [Part 5 of our Get Started with Grafana Alerting tutorial](http://www.grafana.com/tutorials/alerting-get-started-pt5/). +{{% /admonition %}} + ## Link alert rules to panels When configuring the alert rule, you can set the dashboard and panel annotations as shown in this [video](https://youtu.be/ClLp-iSoaSY?si=qKWnvSVaQuvYcuw9&t=170). diff --git a/docs/sources/alerting/fundamentals/alert-rules/queries-conditions.md b/docs/sources/alerting/fundamentals/alert-rules/queries-conditions.md index 582d93d958e..175306e8ef1 100644 --- a/docs/sources/alerting/fundamentals/alert-rules/queries-conditions.md +++ b/docs/sources/alerting/fundamentals/alert-rules/queries-conditions.md @@ -109,8 +109,14 @@ The threshold expression allows the comparison between two single values. Availa - **Is above**: `$A > 5` - **Is below**: `$B < 3` +- **Is equal to**: `$A == 2` +- **Is not equal to**: `$B =! 4` +- **Is above or equal to**: `$A >= 8` +- **Is below or equal to**: `$B <= 16` - **Is within range**: `$A > 0 AND $A < 10` - **Is outside range**: `$B < 0 OR $B > 100` +- **Is within range included**: `$A >= 0 AND $A <= 10` +- **Is outside range included**: `$B <= 0 OR $B >= 100` A threshold returns `0` when the condition is false and `1` when true. diff --git a/docs/sources/developers/http_api/dashboard.md b/docs/sources/developers/http_api/dashboard.md index fe08dc99f15..54bfaf63156 100644 --- a/docs/sources/developers/http_api/dashboard.md +++ b/docs/sources/developers/http_api/dashboard.md @@ -355,7 +355,7 @@ Content-Type: application/json Status Codes: -- **200** – Deleted +- **200** – Restored - **401** – Unauthorized - **403** – Access denied - **404** – Not found diff --git a/docs/sources/fundamentals/exemplars/index.md b/docs/sources/fundamentals/exemplars/index.md index 363bc122b47..4181c50cac5 100644 --- a/docs/sources/fundamentals/exemplars/index.md +++ b/docs/sources/fundamentals/exemplars/index.md @@ -20,21 +20,31 @@ weight: 800 # Introduction to exemplars -An exemplar is a specific trace representative of measurement taken in a given time interval. While metrics excel at giving you an aggregated view of your system, traces give you a fine grained view of a single request; exemplars are a way to link the two. +An exemplar is a specific trace representative of measurement taken in a given time interval. +While metrics excel at giving you an aggregated view of your system, traces give you a fine grained view of a single request; exemplars are a way to link the two. -Suppose your company website is experiencing a surge in traffic volumes. While more than eighty percent of the users are able to access the website in under two seconds, some users are experiencing a higher than normal response time resulting in bad user experience. +Suppose your company website is experiencing a surge in traffic volumes. +While more than eighty percent of the users are able to access the website in under two seconds, some users are experiencing a higher than normal response time resulting in bad user experience. -To identify the factors that are contributing to the latency, you must compare a trace for a fast response against a trace for a slow response. Given the vast amount of data in a typical production environment, it will be extremely laborious and time-consuming effort. +To identify the factors that are contributing to the latency, you must compare a trace for a fast response against a trace for a slow response. +Given the vast amount of data in a typical production environment, it's an extremely laborious and time-consuming effort. -Use exemplars to help isolate problems within your data distribution by pinpointing query traces exhibiting high latency within a time interval. Once you localize the latency problem to a few exemplar traces, you can combine it with additional system based information or location properties to perform a root cause analysis faster, leading to quick resolutions to performance issues. +Use exemplars to help isolate problems within your data distribution by pinpointing query traces exhibiting high latency within a time interval. +After you localize the latency problem to a few exemplar traces, you can combine it with additional system based information or location properties to perform a root cause analysis faster, leading to quick resolutions to performance issues. -Support for exemplars is available for the Prometheus data source only. Once you enable the functionality, exemplar data is available by default. For more information on exemplar configuration and how to enable exemplars, refer to [configuring exemplars in the Prometheus data source]({{< relref "../../datasources/prometheus/configure-prometheus-data-source#exemplars" >}}). +Support for exemplars is available for the Prometheus data source only. +After you enable the functionality, exemplar data is available by default. +For more information on exemplar configuration and how to enable exemplars, refer to [configuring exemplars in the Prometheus data source](../../datasources/prometheus/configure-prometheus-data-source/#exemplars). -Grafana shows exemplars alongside a metric in the Explore view and in dashboards. Each exemplar displays as a highlighted star. You can hover your cursor over an exemplar to view the unique trace ID, which is a combination of a key value pair. To investigate further, click the blue button next to the `traceID` property. +Grafana shows exemplars alongside a metric in the Explore view and in dashboards. +Each exemplar displays as a highlighted star. +You can hover your cursor over an exemplar to view the unique trace ID, which is a combination of a key value pair. +To investigate further, click the blue button next to the `traceID` property. {{< figure src="/media/docs/grafana/exemplars/screenshot-exemplars.png" class="docs-image--no-shadow" max-width= "750px" caption="Screenshot showing the detail window of an exemplar" >}} -Refer to [View exemplar data]({{< relref "#view-exemplar-data" >}}) for instructions on how to drill down and view exemplar trace details from metrics and logs. To know more about exemplars, refer to the blogpost [Intro to exemplars, which enable Grafana Tempo’s distributed tracing at massive scale](/blog/2021/03/31/intro-to-exemplars-which-enable-grafana-tempos-distributed-tracing-at-massive-scale/). +Refer to [View exemplar data](#view-exemplar-data) for instructions on how to drill down and view exemplar trace details from metrics and logs. +To know more about exemplars, refer to the blog post [Intro to exemplars, which enable Grafana Tempo’s distributed tracing at massive scale](/blog/2021/03/31/intro-to-exemplars-which-enable-grafana-tempos-distributed-tracing-at-massive-scale/). ## View exemplar data @@ -42,15 +52,19 @@ When support for exemplar support is enabled for a Prometheus data source, you c ### In Explore -Explore visualizes exemplar traces as highlighted stars alongside metrics data. For more information on how Explore visualizes trace data, refer to [Tracing in Explore]({{< relref "../../explore/trace-integration" >}}). +Explore visualizes exemplar traces as highlighted stars alongside metrics data. +For more information on how Explore visualizes trace data, refer to [Tracing in Explore](../../explore/trace-integration/). To examine the details of an exemplar trace: -1. Place your cursor over an exemplar (highlighted star). Depending on the trace data source you are using, you will see a blue button with the label `Query with `. In the following example, the tracing data source is Tempo. +1. Place your cursor over an exemplar (highlighted star). + Depending on the trace data source you are using, you may see a blue button with the label `Query with `. + In the following example, the tracing data source is Tempo. {{< figure src="/media/docs/grafana/exemplars/screenshot-exemplar-details.png" class="docs-image--no-shadow" max-width= "350px" caption="Screenshot showing exemplar details" >}} -1. Click the **Query with Tempo** option next to the `traceID` property. The trace details, including the spans within the trace are listed in a separate panel on the right. +1. Click the **Query with Tempo** option next to the `traceID` property. + The trace details, including the spans within the trace are listed in a separate panel on the right. {{< figure src="/media/docs/grafana/exemplars/screenshot-exemplar-explore-view.png" class="docs-image--no-shadow" max-width= "900px" caption="Explorer view with panel showing trace details" >}} @@ -58,13 +72,20 @@ For more information on how to drill down and analyze the trace and span details ### In logs -You can also view exemplar trace details from the Loki logs in Explore. Use regex within the Derived fields links for Loki to extract the `traceID` information. Now when you expand Loki logs, you can see a `traceID` property under the **Detected fields** section. To learn more about how to extract a part of a log message into an internal or external link, refer to [using derived fields in Loki]({{< relref "../../explore/logs-integration" >}}). +You can also view exemplar trace details from the Loki logs in Explore. +Use regular expressions within the Derived fields links for Loki to extract the `traceID` information. +Now when you expand Loki logs, you can see a `traceID` property under the **Detected fields** section. +To learn more about how to extract a part of a log message into an internal or external link, refer to [using derived fields in Loki](../../explore/logs-integration/). To view the details of an exemplar trace: -1. Expand a log line and scroll down to the `Fields` section. Depending on your backend trace data source, you will see a blue button with the label ``. +1. Expand a log line and scroll down to the `Fields` section. + Depending on your backend trace data source, you may see a blue button with the label ``. -1. Click the blue button next to the `traceID` property. Typically, it will have the name of the backend data source. In the following example, the tracing data source is Tempo. The trace details, including the spans within the trace are listed in a separate panel on the right. +1. Click the blue button next to the `traceID` property. + Typically, it has the name of the backend data source. + In the following example, the tracing data source is Tempo. + The trace details, including the spans within the trace are listed in a separate panel on the right. {{< figure src="/media/docs/grafana/exemplars/screenshot-exemplar-loki-logs.png" class="docs-image--no-shadow" max-width= "750px" caption="Explorer view with panel showing trace details" >}} @@ -78,16 +99,20 @@ This panel shows the details of the trace in different segments. You can add more traces to the results using the `Add query` button. -- The next segment shows the entire span for the specific trace as a narrow strip. All levels of the trace from the client all the way down to database query is displayed, which provides a bird's eye view of the time distribution across all layers over which the HTTP request was processed. +- The next segment shows the entire span for the specific trace as a narrow strip. + All levels of the trace from the client all the way down to database query is displayed, which provides a bird's eye view of the time distribution across all layers over which the HTTP request was processed. 1. You can click within this strip view to display a magnified view of a smaller time segment within the span. This magnified view shows up in the bottom segment of the panel. 1. In the magnified view, you can expand or collapse the various levels of the trace to drill down to the specific span of interest. - For example, if the strip view shows that most of the latency was within the app layer, you can expand the trace down the app layer to investigate the problem further. To expand a particular layer of span, click the left icon. The same button can collapse an expanded span. + For example, if the strip view shows that most of the latency was within the app layer, you can expand the trace down the app layer to investigate the problem further. + To expand a particular layer of span, click the left icon. + The same button can collapse an expanded span. - To see the details of the span at any level, click the span itself. - This displays additional metadata associated with the span. The metadata itself is initially shown in a narrow strip but you can see more details by clicking the metadata strip. + This displays additional metadata associated with the span. + The metadata itself is initially shown in a narrow strip but you can see more details by clicking the metadata strip. {{< figure src="/media/docs/grafana/exemplars/screenshot-exemplar-span-details.png" class="docs-image--no-shadow" max-width= "600px" caption="Span details" >}} diff --git a/docs/sources/setup-grafana/installation/_index.md b/docs/sources/setup-grafana/installation/_index.md index 940a80aa91c..44c69f8ab47 100644 --- a/docs/sources/setup-grafana/installation/_index.md +++ b/docs/sources/setup-grafana/installation/_index.md @@ -32,7 +32,7 @@ Grafana relies on other open source software to operate. For a list of open sour Grafana supports the following operating systems: - [Debian or Ubuntu]({{< relref "./debian" >}}) -- [Red Hat, RHEL, or Fedora]({{< relref "./redhat-rhel-fedora" >}}) +- [RHEL or Fedora]({{< relref "./rhel-fedora" >}}) - [SUSE or openSUSE]({{< relref "./suse-opensuse" >}}) - [macOS]({{< relref "./mac" >}}) - [Windows]({{< relref "./windows" >}}) diff --git a/e2e/old-arch/dashboards-suite/import-dashboard.spec.ts b/e2e/old-arch/dashboards-suite/import-dashboard.spec.ts index 6d4bfed5e68..55d58705111 100644 --- a/e2e/old-arch/dashboards-suite/import-dashboard.spec.ts +++ b/e2e/old-arch/dashboards-suite/import-dashboard.spec.ts @@ -6,7 +6,7 @@ describe('Import Dashboards Test', () => { e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); }); - it('Ensure you can import a number of json test dashboards from a specific test directory', () => { + it.skip('Ensure you can import a number of json test dashboards from a specific test directory', () => { e2e.flows.importDashboard(testDashboard, 1000); }); }); diff --git a/go.mod b/go.mod index d512579c149..ae2caa2aaef 100644 --- a/go.mod +++ b/go.mod @@ -71,7 +71,7 @@ require ( github.com/googleapis/gax-go/v2 v2.14.1 // @grafana/grafana-backend-group github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group github.com/gorilla/websocket v1.5.3 // @grafana/grafana-app-platform-squad - github.com/grafana/alerting v0.0.0-20250221202230-9d7e00921e44 // @grafana/alerting-backend + github.com/grafana/alerting v0.0.0-20250224133628-2acbeef29642 // @grafana/alerting-backend github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 // @grafana/identity-access-team github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 // @grafana/identity-access-team github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics @@ -167,13 +167,13 @@ require ( go.uber.org/goleak v1.3.0 // @grafana/grafana-search-and-storage go.uber.org/zap v1.27.0 // @grafana/identity-access-team gocloud.dev v0.40.0 // @grafana/grafana-app-platform-squad - golang.org/x/crypto v0.32.0 // @grafana/grafana-backend-group + golang.org/x/crypto v0.35.0 // @grafana/grafana-backend-group golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // @grafana/alerting-backend golang.org/x/mod v0.22.0 // indirect; @grafana/grafana-backend-group - golang.org/x/net v0.34.0 // @grafana/oss-big-tent @grafana/partner-datasources - golang.org/x/oauth2 v0.26.0 // @grafana/identity-access-team + golang.org/x/net v0.35.0 // @grafana/oss-big-tent @grafana/partner-datasources + golang.org/x/oauth2 v0.27.0 // @grafana/identity-access-team golang.org/x/sync v0.11.0 // @grafana/alerting-backend - golang.org/x/text v0.21.0 // @grafana/grafana-backend-group + golang.org/x/text v0.22.0 // @grafana/grafana-backend-group golang.org/x/time v0.9.0 // @grafana/grafana-backend-group golang.org/x/tools v0.29.0 // indirect; @grafana/grafana-as-code gonum.org/v1/gonum v0.15.1 // @grafana/oss-big-tent @@ -520,7 +520,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect go4.org/netipx v0.0.0-20230125063823-8449b0a6169f // indirect golang.org/x/sys v0.30.0 // indirect - golang.org/x/term v0.28.0 // indirect + golang.org/x/term v0.29.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect diff --git a/go.sum b/go.sum index 2b409ed61b5..edbbd7eb159 100644 --- a/go.sum +++ b/go.sum @@ -1511,8 +1511,8 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/alerting v0.0.0-20250221202230-9d7e00921e44 h1:vboqvbAO0s0CTALHnqfmNvhCP1ziBcZNpYDbORqvOgg= -github.com/grafana/alerting v0.0.0-20250221202230-9d7e00921e44/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= +github.com/grafana/alerting v0.0.0-20250224133628-2acbeef29642 h1:iQ0h/h+QoguSZDF+ZpPxcM/C+m1kjh+aXjMpxywowPA= +github.com/grafana/alerting v0.0.0-20250224133628-2acbeef29642/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 h1:NTMmow+74I3Jb033xhbRgWQS7A//5TDhiM4tl7bsVP4= github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7/go.mod h1:T3X4z0ejGfJOiOmZLFeKCRT/yxWJq/RtclAc/PHj/w4= github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 h1:EokLC5grHwLPs4tXW8T6E8187H1e5G9AP0QQ5B60HbA= @@ -2552,8 +2552,8 @@ golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2699,8 +2699,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -2736,8 +2736,8 @@ golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4 golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= -golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= -golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -2904,8 +2904,8 @@ golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2925,8 +2925,8 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/go.work.sum b/go.work.sum index 6d37067853e..3f976f0bfe3 100644 --- a/go.work.sum +++ b/go.work.sum @@ -619,6 +619,7 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/alerting v0.0.0-20250129195454-3e5b80036b7a/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= +github.com/grafana/alerting v0.0.0-20250221202230-9d7e00921e44/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= github.com/grafana/authlib v0.0.0-20250123104008-e99947858901/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8= github.com/grafana/authlib/types v0.0.0-20250120144156-d6737a7dc8f5/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= @@ -1112,6 +1113,8 @@ golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5D golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= @@ -1137,6 +1140,7 @@ golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/package.json b/package.json index cc2ba8bfcf8..012ee9da149 100644 --- a/package.json +++ b/package.json @@ -376,7 +376,7 @@ "react-highlight-words": "0.21.0", "react-hook-form": "^7.49.2", "react-i18next": "^15.0.0", - "react-inlinesvg": "4.1.5", + "react-inlinesvg": "4.2.0", "react-loading-skeleton": "3.5.0", "react-moveable": "0.56.0", "react-redux": "9.2.0", diff --git a/packages/grafana-data/rollup.config.ts b/packages/grafana-data/rollup.config.ts index 9894024f99c..5a913449466 100644 --- a/packages/grafana-data/rollup.config.ts +++ b/packages/grafana-data/rollup.config.ts @@ -1,53 +1,15 @@ -import resolve from '@rollup/plugin-node-resolve'; import { createRequire } from 'node:module'; -import path from 'path'; -import dts from 'rollup-plugin-dts'; -import esbuild from 'rollup-plugin-esbuild'; -import { nodeExternals } from 'rollup-plugin-node-externals'; + +import { entryPoint, plugins, esmOutput, cjsOutput, tsDeclarationOutput } from '../rollup.config.parts'; const rq = createRequire(import.meta.url); const pkg = rq('./package.json'); -const legacyOutputDefaults = { - esModule: true, - interop: 'compat', -}; - export default [ { - input: 'src/index.ts', - plugins: [ - nodeExternals({ deps: true, packagePath: './package.json' }), - resolve(), - esbuild({ - target: 'es2018', - tsconfig: 'tsconfig.build.json', - }), - ], - output: [ - { - format: 'cjs', - sourcemap: true, - dir: path.dirname(pkg.publishConfig.main), - ...legacyOutputDefaults, - }, - { - format: 'esm', - sourcemap: true, - dir: path.dirname(pkg.publishConfig.module), - preserveModules: true, - // @ts-expect-error (TS cannot assure that `process.env.PROJECT_CWD` is a string) - preserveModulesRoot: path.join(process.env.PROJECT_CWD, `packages/grafana-data/src`), - ...legacyOutputDefaults, - }, - ], - }, - { - input: './compiled/index.d.ts', - plugins: [dts()], - output: { - file: pkg.publishConfig.types, - format: 'es', - }, + input: entryPoint, + plugins, + output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-data')], }, + tsDeclarationOutput(pkg), ]; diff --git a/packages/grafana-data/src/field/standardFieldConfigEditorRegistry.ts b/packages/grafana-data/src/field/standardFieldConfigEditorRegistry.ts index 83b7f5050e3..584c3779d23 100644 --- a/packages/grafana-data/src/field/standardFieldConfigEditorRegistry.ts +++ b/packages/grafana-data/src/field/standardFieldConfigEditorRegistry.ts @@ -16,6 +16,7 @@ export interface StandardEditorContext { options?: TOptions; instanceState?: TState; isOverride?: boolean; + annotations?: DataFrame[]; } export interface StandardEditorProps { diff --git a/packages/grafana-data/src/types/app.ts b/packages/grafana-data/src/types/app.ts index 23e52f913b8..38a20ad12b8 100644 --- a/packages/grafana-data/src/types/app.ts +++ b/packages/grafana-data/src/types/app.ts @@ -179,4 +179,6 @@ export enum FeatureState { privatePreview = 'private preview', /** used to mark features that are in public preview with low/medium risk, or as a shared badge for public and private previews */ preview = 'preview', + /** used to mark new GA features */ + new = 'new', } diff --git a/packages/grafana-data/src/types/options.ts b/packages/grafana-data/src/types/options.ts index d23ceb35c5e..84f45884a36 100644 --- a/packages/grafana-data/src/types/options.ts +++ b/packages/grafana-data/src/types/options.ts @@ -51,5 +51,5 @@ export interface OptionEditorConfig { /** * Function that enables configuration of when option editor should be shown based on current panel option properties. */ - showIf?: (currentOptions: TOptions, data?: DataFrame[]) => boolean | undefined; + showIf?: (currentOptions: TOptions, data?: DataFrame[], annotations?: DataFrame[]) => boolean | undefined; } diff --git a/packages/grafana-data/src/types/time.ts b/packages/grafana-data/src/types/time.ts index f3eeefac9ce..688c896ee6e 100644 --- a/packages/grafana-data/src/types/time.ts +++ b/packages/grafana-data/src/types/time.ts @@ -76,7 +76,7 @@ export function getDefaultTimeRange(): TimeRange { } /** - * Returns the default realtive time range. + * Returns the default relative time range. * * @public */ diff --git a/packages/grafana-e2e-selectors/rollup.config.ts b/packages/grafana-e2e-selectors/rollup.config.ts index b70662c33a8..338dae23ac9 100644 --- a/packages/grafana-e2e-selectors/rollup.config.ts +++ b/packages/grafana-e2e-selectors/rollup.config.ts @@ -1,53 +1,15 @@ -import resolve from '@rollup/plugin-node-resolve'; import { createRequire } from 'node:module'; -import path from 'path'; -import dts from 'rollup-plugin-dts'; -import esbuild from 'rollup-plugin-esbuild'; -import { nodeExternals } from 'rollup-plugin-node-externals'; + +import { cjsOutput, entryPoint, esmOutput, plugins, tsDeclarationOutput } from '../rollup.config.parts'; const rq = createRequire(import.meta.url); const pkg = rq('./package.json'); -const legacyOutputDefaults = { - esModule: true, - interop: 'compat', -}; - export default [ { - input: 'src/index.ts', - plugins: [ - nodeExternals({ deps: true, packagePath: './package.json' }), - resolve(), - esbuild({ - target: 'es2018', - tsconfig: 'tsconfig.build.json', - }), - ], - output: [ - { - format: 'cjs', - sourcemap: true, - dir: path.dirname(pkg.publishConfig.main), - ...legacyOutputDefaults, - }, - { - format: 'esm', - sourcemap: true, - dir: path.dirname(pkg.publishConfig.module), - preserveModules: true, - // @ts-expect-error (TS cannot assure that `process.env.PROJECT_CWD` is a string) - preserveModulesRoot: path.join(process.env.PROJECT_CWD, `packages/grafana-e2e-selectors/src`), - ...legacyOutputDefaults, - }, - ], - }, - { - input: './compiled/index.d.ts', - plugins: [dts()], - output: { - file: pkg.publishConfig.types, - format: 'es', - }, + input: entryPoint, + plugins, + output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-e2e-selectors')], }, + tsDeclarationOutput(pkg), ]; diff --git a/packages/grafana-flamegraph/rollup.config.ts b/packages/grafana-flamegraph/rollup.config.ts index 86d0ad86cfa..dbd64b4abdb 100644 --- a/packages/grafana-flamegraph/rollup.config.ts +++ b/packages/grafana-flamegraph/rollup.config.ts @@ -1,53 +1,15 @@ -import resolve from '@rollup/plugin-node-resolve'; import { createRequire } from 'node:module'; -import path from 'path'; -import dts from 'rollup-plugin-dts'; -import esbuild from 'rollup-plugin-esbuild'; -import { nodeExternals } from 'rollup-plugin-node-externals'; + +import { cjsOutput, entryPoint, esmOutput, plugins, tsDeclarationOutput } from '../rollup.config.parts'; const rq = createRequire(import.meta.url); const pkg = rq('./package.json'); -const legacyOutputDefaults = { - esModule: true, - interop: 'compat', -}; - export default [ { - input: 'src/index.ts', - plugins: [ - nodeExternals({ deps: true, packagePath: './package.json' }), - resolve(), - esbuild({ - target: 'es2018', - tsconfig: 'tsconfig.build.json', - }), - ], - output: [ - { - format: 'cjs', - sourcemap: true, - dir: path.dirname(pkg.publishConfig.main), - ...legacyOutputDefaults, - }, - { - format: 'esm', - sourcemap: true, - dir: path.dirname(pkg.publishConfig.module), - preserveModules: true, - // @ts-expect-error (TS cannot assure that `process.env.PROJECT_CWD` is a string) - preserveModulesRoot: path.join(process.env.PROJECT_CWD, `packages/grafana-ui/src`), - ...legacyOutputDefaults, - }, - ], - }, - { - input: './compiled/index.d.ts', - plugins: [dts()], - output: { - file: pkg.publishConfig.types, - format: 'es', - }, + input: entryPoint, + plugins, + output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-flamegraph')], }, + tsDeclarationOutput(pkg), ]; diff --git a/packages/grafana-icons/package.json b/packages/grafana-icons/package.json index dec83cf36f8..c08c70e14ed 100644 --- a/packages/grafana-icons/package.json +++ b/packages/grafana-icons/package.json @@ -16,6 +16,7 @@ "types": "src/index.ts", "publishConfig": { "main": "dist/index.js", + "module": "dist/index.js", "types": "dist/index.d.ts", "access": "public" }, diff --git a/packages/grafana-icons/rollup.config.ts b/packages/grafana-icons/rollup.config.ts index 6bf3c3b536e..fde6ca115b0 100644 --- a/packages/grafana-icons/rollup.config.ts +++ b/packages/grafana-icons/rollup.config.ts @@ -1,45 +1,15 @@ -import resolve from '@rollup/plugin-node-resolve'; import { createRequire } from 'node:module'; -import path from 'path'; -import dts from 'rollup-plugin-dts'; -import esbuild from 'rollup-plugin-esbuild'; -import { nodeExternals } from 'rollup-plugin-node-externals'; + +import { entryPoint, esmOutput, plugins, tsDeclarationOutput } from '../rollup.config.parts'; const rq = createRequire(import.meta.url); const pkg = rq('./package.json'); -const legacyOutputDefaults = { - esModule: true, - interop: 'compat', -}; - export default [ { - input: 'src/index.ts', - plugins: [ - nodeExternals({ deps: true, packagePath: './package.json' }), - resolve(), - esbuild({ - target: 'es2018', - tsconfig: 'tsconfig.build.json', - }), - ], - output: [ - { - format: 'esm', - sourcemap: true, - dir: path.dirname(pkg.publishConfig.main), - preserveModules: true, - ...legacyOutputDefaults, - }, - ], - }, - { - input: 'src/index.ts', - plugins: [dts()], - output: { - file: pkg.publishConfig.types, - format: 'es', - }, + input: entryPoint, + plugins, + output: esmOutput(pkg, 'grafana-icons'), }, + tsDeclarationOutput(pkg, { input: 'src/index.ts' }), ]; diff --git a/packages/grafana-prometheus/rollup.config.ts b/packages/grafana-prometheus/rollup.config.ts index 7e630257c36..5c7d9dd3c04 100644 --- a/packages/grafana-prometheus/rollup.config.ts +++ b/packages/grafana-prometheus/rollup.config.ts @@ -1,55 +1,16 @@ import image from '@rollup/plugin-image'; -import resolve from '@rollup/plugin-node-resolve'; import { createRequire } from 'node:module'; -import path from 'path'; -import dts from 'rollup-plugin-dts'; -import esbuild from 'rollup-plugin-esbuild'; -import { nodeExternals } from 'rollup-plugin-node-externals'; + +import { cjsOutput, entryPoint, esmOutput, plugins, tsDeclarationOutput } from '../rollup.config.parts'; const rq = createRequire(import.meta.url); const pkg = rq('./package.json'); -const legacyOutputDefaults = { - esModule: true, - interop: 'compat', -}; - export default [ { - input: 'src/index.ts', - plugins: [ - nodeExternals({ deps: true, packagePath: './package.json' }), - resolve(), - esbuild({ - target: 'es2018', - tsconfig: 'tsconfig.build.json', - }), - image(), - ], - output: [ - { - format: 'cjs', - sourcemap: true, - dir: path.dirname(pkg.publishConfig.main), - ...legacyOutputDefaults, - }, - { - format: 'esm', - sourcemap: true, - dir: path.dirname(pkg.publishConfig.module), - preserveModules: true, - // @ts-expect-error (TS cannot assure that `process.env.PROJECT_CWD` is a string) - preserveModulesRoot: path.join(process.env.PROJECT_CWD, `packages/grafana-prometheus/src`), - ...legacyOutputDefaults, - }, - ], - }, - { - input: './compiled/index.d.ts', - plugins: [dts()], - output: { - file: pkg.publishConfig.types, - format: 'es', - }, + input: entryPoint, + plugins: [...plugins, image()], + output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-prometheus')], }, + tsDeclarationOutput(pkg), ]; diff --git a/packages/grafana-runtime/rollup.config.ts b/packages/grafana-runtime/rollup.config.ts index 25992a93441..02f523deee1 100644 --- a/packages/grafana-runtime/rollup.config.ts +++ b/packages/grafana-runtime/rollup.config.ts @@ -1,53 +1,15 @@ -import resolve from '@rollup/plugin-node-resolve'; import { createRequire } from 'node:module'; -import path from 'path'; -import dts from 'rollup-plugin-dts'; -import esbuild from 'rollup-plugin-esbuild'; -import { nodeExternals } from 'rollup-plugin-node-externals'; + +import { cjsOutput, entryPoint, esmOutput, plugins, tsDeclarationOutput } from '../rollup.config.parts'; const rq = createRequire(import.meta.url); const pkg = rq('./package.json'); -const legacyOutputDefaults = { - esModule: true, - interop: 'compat', -}; - export default [ { - input: 'src/index.ts', - plugins: [ - nodeExternals({ deps: true, packagePath: './package.json' }), - resolve(), - esbuild({ - target: 'es2018', - tsconfig: 'tsconfig.build.json', - }), - ], - output: [ - { - format: 'cjs', - sourcemap: true, - dir: path.dirname(pkg.publishConfig.main), - ...legacyOutputDefaults, - }, - { - format: 'esm', - sourcemap: true, - dir: path.dirname(pkg.publishConfig.module), - preserveModules: true, - // @ts-expect-error (TS cannot assure that `process.env.PROJECT_CWD` is a string) - preserveModulesRoot: path.join(process.env.PROJECT_CWD, `packages/grafana-runtime/src`), - ...legacyOutputDefaults, - }, - ], - }, - { - input: './compiled/index.d.ts', - plugins: [dts()], - output: { - file: pkg.publishConfig.types, - format: 'es', - }, + input: entryPoint, + plugins, + output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-runtime')], }, + tsDeclarationOutput(pkg), ]; diff --git a/packages/grafana-schema/rollup.config.ts b/packages/grafana-schema/rollup.config.ts index 6a2f736c944..aa0c96aff06 100644 --- a/packages/grafana-schema/rollup.config.ts +++ b/packages/grafana-schema/rollup.config.ts @@ -1,57 +1,22 @@ -import resolve from '@rollup/plugin-node-resolve'; import { glob } from 'glob'; import { createRequire } from 'node:module'; import { fileURLToPath } from 'node:url'; import path from 'path'; -import dts from 'rollup-plugin-dts'; -import esbuild from 'rollup-plugin-esbuild'; -import { nodeExternals } from 'rollup-plugin-node-externals'; + +import { cjsOutput, entryPoint, esmOutput, plugins, tsDeclarationOutput } from '../rollup.config.parts'; const rq = createRequire(import.meta.url); const pkg = rq('./package.json'); -const legacyOutputDefaults = { - esModule: true, - interop: 'compat', -}; +const [_, noderesolve, esbuild] = plugins; export default [ { - input: 'src/index.ts', - plugins: [ - nodeExternals({ deps: true, packagePath: './package.json' }), - resolve(), - esbuild({ - target: 'es2018', - tsconfig: 'tsconfig.build.json', - }), - ], - output: [ - { - format: 'cjs', - sourcemap: true, - dir: path.dirname(pkg.publishConfig.main), - ...legacyOutputDefaults, - }, - { - format: 'esm', - sourcemap: true, - dir: path.dirname(pkg.publishConfig.module), - preserveModules: true, - // @ts-expect-error (TS cannot assure that `process.env.PROJECT_CWD` is a string) - preserveModulesRoot: path.join(process.env.PROJECT_CWD, `packages/grafana-schema/src`), - ...legacyOutputDefaults, - }, - ], - }, - { - input: './dist/esm/index.d.ts', - plugins: [dts()], - output: { - file: pkg.publishConfig.types, - format: 'es', - }, + input: entryPoint, + plugins, + output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-schema')], }, + tsDeclarationOutput(pkg, { input: './dist/esm/index.d.ts' }), { input: Object.fromEntries( glob @@ -61,13 +26,7 @@ export default [ fileURLToPath(new URL(file, import.meta.url)), ]) ), - plugins: [ - resolve(), - esbuild({ - target: 'es2018', - tsconfig: 'tsconfig.build.json', - }), - ], + plugins: [noderesolve, esbuild], output: { format: 'esm', dir: path.dirname(pkg.publishConfig.module), diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index eff1830e872..87cc03f93b7 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -95,7 +95,7 @@ "react-highlight-words": "0.21.0", "react-hook-form": "^7.49.2", "react-i18next": "^15.0.0", - "react-inlinesvg": "4.1.5", + "react-inlinesvg": "4.2.0", "react-loading-skeleton": "3.5.0", "react-router-dom": "5.3.4", "react-router-dom-v5-compat": "^6.26.1", diff --git a/packages/grafana-ui/rollup.config.ts b/packages/grafana-ui/rollup.config.ts index b18618dd7ea..d3cb5c2d8b8 100644 --- a/packages/grafana-ui/rollup.config.ts +++ b/packages/grafana-ui/rollup.config.ts @@ -1,12 +1,9 @@ -import resolve from '@rollup/plugin-node-resolve'; import { createRequire } from 'node:module'; -import path from 'path'; import copy from 'rollup-plugin-copy'; -import dts from 'rollup-plugin-dts'; -import esbuild from 'rollup-plugin-esbuild'; -import { nodeExternals } from 'rollup-plugin-node-externals'; import svg from 'rollup-plugin-svg-import'; +import { cjsOutput, entryPoint, esmOutput, plugins, tsDeclarationOutput } from '../rollup.config.parts'; + const rq = createRequire(import.meta.url); const icons = rq('../../public/app/core/icons/cached.json'); const pkg = rq('./package.json'); @@ -15,51 +12,18 @@ const iconSrcPaths = icons.map((iconSubPath) => { return `../../public/img/icons/${iconSubPath}.svg`; }); -const legacyOutputDefaults = { - esModule: true, - interop: 'compat', -}; - export default [ { - input: 'src/index.ts', + input: entryPoint, plugins: [ - nodeExternals({ deps: true, packagePath: './package.json' }), + ...plugins, svg({ stringify: true }), - resolve(), copy({ targets: [{ src: iconSrcPaths, dest: './dist/public/' }], flatten: false, }), - esbuild({ - target: 'es2018', - tsconfig: 'tsconfig.build.json', - }), - ], - output: [ - { - format: 'cjs', - sourcemap: true, - dir: path.dirname(pkg.publishConfig.main), - ...legacyOutputDefaults, - }, - { - format: 'esm', - sourcemap: true, - dir: path.dirname(pkg.publishConfig.module), - preserveModules: true, - // @ts-expect-error (TS cannot assure that `process.env.PROJECT_CWD` is a string) - preserveModulesRoot: path.join(process.env.PROJECT_CWD, `packages/grafana-ui/src`), - ...legacyOutputDefaults, - }, ], + output: [cjsOutput(pkg), esmOutput(pkg, 'grafana-ui')], }, - { - input: './compiled/index.d.ts', - plugins: [dts()], - output: { - file: pkg.publishConfig.types, - format: 'es', - }, - }, + tsDeclarationOutput(pkg), ]; diff --git a/packages/grafana-ui/src/components/Badge/Badge.tsx b/packages/grafana-ui/src/components/Badge/Badge.tsx index 73eca08b108..59cd9074a2d 100644 --- a/packages/grafana-ui/src/components/Badge/Badge.tsx +++ b/packages/grafana-ui/src/components/Badge/Badge.tsx @@ -79,7 +79,7 @@ const getStyles = (theme: GrafanaTheme2, color: BadgeColor) => { border: `1px solid ${borderColor}`, color: textColor, fontWeight: theme.typography.fontWeightRegular, - gap: '2px', + gap: theme.spacing(0.5), fontSize: theme.typography.bodySmall.fontSize, lineHeight: theme.typography.bodySmall.lineHeight, alignItems: 'center', diff --git a/packages/grafana-ui/src/components/FeatureBadge/FeatureBadge.tsx b/packages/grafana-ui/src/components/FeatureBadge/FeatureBadge.tsx index 8ecd1d54528..8265ada629b 100644 --- a/packages/grafana-ui/src/components/FeatureBadge/FeatureBadge.tsx +++ b/packages/grafana-ui/src/components/FeatureBadge/FeatureBadge.tsx @@ -1,5 +1,6 @@ import { FeatureState } from '@grafana/data'; +import { t } from '../../utils/i18n'; import { Badge, BadgeProps } from '../Badge/Badge'; export interface FeatureBadgeProps { @@ -30,21 +31,28 @@ function getPanelStateBadgeDisplayModel(featureState: FeatureState): BadgeProps case FeatureState.experimental: return { - text: 'Experimental', + text: t('grafana-ui.feature-badge.experimental', 'Experimental'), icon: 'exclamation-triangle', color: 'orange', }; case FeatureState.preview: return { - text: 'Preview', + text: t('grafana-ui.feature-badge.preview', 'Preview'), icon: 'rocket', color: 'blue', }; case FeatureState.privatePreview: return { - text: 'Private preview', + text: t('grafana-ui.feature-badge.private-preview', 'Private preview'), + icon: 'rocket', + color: 'blue', + }; + + case FeatureState.new: + return { + text: t('grafana-ui.feature-badge.new', 'New!'), icon: 'rocket', color: 'blue', }; diff --git a/packages/grafana-ui/src/options/builder/tooltip.tsx b/packages/grafana-ui/src/options/builder/tooltip.tsx index 3679ac18df8..7a9b1f59e1a 100644 --- a/packages/grafana-ui/src/options/builder/tooltip.tsx +++ b/packages/grafana-ui/src/options/builder/tooltip.tsx @@ -1,4 +1,4 @@ -import { PanelOptionsEditorBuilder } from '@grafana/data'; +import { DataFrame, PanelOptionsEditorBuilder } from '@grafana/data'; import { OptionsWithTooltip, TooltipDisplayMode, SortOrder } from '@grafana/schema'; /** @internal */ @@ -94,6 +94,13 @@ export function addTooltipOptions( settings: { integer: true, }, - showIf: (options: T) => options.tooltip?.mode === TooltipDisplayMode.Multi, + showIf: (options: T, data: DataFrame[] | undefined, annotations: DataFrame[] | undefined) => { + return ( + options.tooltip?.mode === TooltipDisplayMode.Multi || + annotations?.some((df) => { + return df.meta?.custom?.resultType === 'exemplar'; + }) + ); + }, }); } diff --git a/packages/rollup.config.parts.ts b/packages/rollup.config.parts.ts new file mode 100644 index 00000000000..ca805b07ed9 --- /dev/null +++ b/packages/rollup.config.parts.ts @@ -0,0 +1,58 @@ +// This file contains the common parts of the rollup configuration that are shared across multiple packages. +import nodeResolve from '@rollup/plugin-node-resolve'; +import { dirname, resolve } from 'node:path'; +import dts from 'rollup-plugin-dts'; +import esbuild from 'rollup-plugin-esbuild'; +import { nodeExternals } from 'rollup-plugin-node-externals'; + +// This is the path to the root of the grafana project +// Prefer PROJECT_CWD env var set by yarn berry +const projectCwd = process.env.PROJECT_CWD ?? '../../'; + +export const entryPoint = 'src/index.ts'; + +// Plugins that are shared across all rollup configurations. Their order can affect build output. +// Externalising and resolving modules should happen before transformation. +export const plugins = [ + nodeExternals({ deps: true, packagePath: './package.json' }), + nodeResolve(), + esbuild({ + target: 'es2018', + tsconfig: 'tsconfig.build.json', + }), +]; + +// Generates a rollup configuration for commonjs output. +export function cjsOutput(pkg) { + return { + format: 'cjs', + sourcemap: true, + dir: dirname(pkg.publishConfig.main), + esModule: true, + interop: 'compat', + }; +} + +// Generate a rollup configuration for es module output. +export function esmOutput(pkg, pkgName) { + return { + format: 'esm', + sourcemap: true, + dir: dirname(pkg.publishConfig.module), + preserveModules: true, + preserveModulesRoot: resolve(projectCwd, `packages/${pkgName}/src`), + }; +} + +// Generate a rollup configuration for rolling up typescript declaration files into a single file. +export function tsDeclarationOutput(pkg, overrides = {}) { + return { + input: './compiled/index.d.ts', + plugins: [dts()], + output: { + file: pkg.publishConfig.types, + format: 'es', + }, + ...overrides, + }; +} diff --git a/pkg/aggregator/go.mod b/pkg/aggregator/go.mod index 2f254da3755..edfe54e00b2 100644 --- a/pkg/aggregator/go.mod +++ b/pkg/aggregator/go.mod @@ -134,15 +134,15 @@ require ( go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.32.0 // indirect + golang.org/x/crypto v0.35.0 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/oauth2 v0.26.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect - golang.org/x/term v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/term v0.29.0 // indirect + golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.9.0 // indirect golang.org/x/tools v0.29.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect diff --git a/pkg/aggregator/go.sum b/pkg/aggregator/go.sum index dcacd54e2da..eb715898bf0 100644 --- a/pkg/aggregator/go.sum +++ b/pkg/aggregator/go.sum @@ -396,8 +396,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= @@ -418,11 +418,11 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= -golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -447,12 +447,12 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index 6608d7f91b3..36b8d4b827e 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -36,11 +36,11 @@ require ( go.opentelemetry.io/otel v1.34.0 // indirect go.opentelemetry.io/otel/sdk v1.34.0 // indirect go.opentelemetry.io/otel/trace v1.34.0 // indirect - golang.org/x/crypto v0.32.0 // indirect - golang.org/x/net v0.34.0 // indirect + golang.org/x/crypto v0.35.0 // indirect + golang.org/x/net v0.35.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/text v0.22.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect google.golang.org/grpc v1.70.0 // indirect google.golang.org/protobuf v1.36.4 // indirect diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index 864e82c4e31..5b3d03a464a 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -89,8 +89,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -103,8 +103,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -135,8 +135,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index 1c208fbbb0c..83a53b3151a 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -81,11 +81,12 @@ require ( go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/oauth2 v0.26.0 // indirect + golang.org/x/crypto v0.35.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sys v0.30.0 // indirect - golang.org/x/term v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/term v0.29.0 // indirect + golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.9.0 // indirect golang.org/x/tools v0.29.0 // indirect google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index 921a0ec5c70..fddf55e1b93 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -233,8 +233,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -251,11 +251,11 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= -golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -269,12 +269,12 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/pkg/build/go.mod b/pkg/build/go.mod index d404a6ce87e..1d5f29a7bed 100644 --- a/pkg/build/go.mod +++ b/pkg/build/go.mod @@ -26,12 +26,12 @@ require ( go.opentelemetry.io/otel v1.34.0 // indirect; @grafana/grafana-backend-group go.opentelemetry.io/otel/sdk v1.34.0 // indirect; @grafana/grafana-backend-group go.opentelemetry.io/otel/trace v1.34.0 // indirect; @grafana/grafana-backend-group - golang.org/x/crypto v0.32.0 // indirect; @grafana/grafana-backend-group + golang.org/x/crypto v0.35.0 // indirect; @grafana/grafana-backend-group golang.org/x/mod v0.22.0 // @grafana/grafana-backend-group - golang.org/x/net v0.34.0 // indirect; @grafana/oss-big-tent @grafana/partner-datasources - golang.org/x/oauth2 v0.26.0 // @grafana/identity-access-team + golang.org/x/net v0.35.0 // indirect; @grafana/oss-big-tent @grafana/partner-datasources + golang.org/x/oauth2 v0.27.0 // @grafana/identity-access-team golang.org/x/sync v0.11.0 // indirect; @grafana/alerting-backend - golang.org/x/text v0.21.0 // indirect; @grafana/grafana-backend-group + golang.org/x/text v0.22.0 // indirect; @grafana/grafana-backend-group golang.org/x/time v0.9.0 // indirect; @grafana/grafana-backend-group google.golang.org/api v0.216.0 // @grafana/grafana-backend-group google.golang.org/grpc v1.70.0 // indirect; @grafana/plugins-platform-backend diff --git a/pkg/build/go.sum b/pkg/build/go.sum index e1c3f80a9a7..7c868c04a4c 100644 --- a/pkg/build/go.sum +++ b/pkg/build/go.sum @@ -267,8 +267,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= @@ -288,11 +288,11 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= -golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -315,8 +315,8 @@ golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= diff --git a/pkg/codegen/go.mod b/pkg/codegen/go.mod index acc83fd50d8..3c3b0bfa94b 100644 --- a/pkg/codegen/go.mod +++ b/pkg/codegen/go.mod @@ -44,9 +44,9 @@ require ( github.com/xlab/treeprint v1.2.0 // indirect github.com/yalue/merged_fs v1.3.0 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/net v0.34.0 // indirect + golang.org/x/net v0.35.0 // indirect golang.org/x/sync v0.11.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/text v0.22.0 // indirect golang.org/x/tools v0.29.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/pkg/codegen/go.sum b/pkg/codegen/go.sum index 445e9e138eb..25214c4b99e 100644 --- a/pkg/codegen/go.sum +++ b/pkg/codegen/go.sum @@ -100,12 +100,12 @@ github.com/yalue/merged_fs v1.3.0 h1:qCeh9tMPNy/i8cwDsQTJ5bLr6IRxbs6meakNE5O+wyY github.com/yalue/merged_fs v1.3.0/go.mod h1:WqqchfVYQyclV2tnR7wtRhBddzBvLVR83Cjw9BKQw0M= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/plugins/codegen/go.mod b/pkg/plugins/codegen/go.mod index 1aa5a648860..5d415a7f052 100644 --- a/pkg/plugins/codegen/go.mod +++ b/pkg/plugins/codegen/go.mod @@ -43,10 +43,10 @@ require ( github.com/xlab/treeprint v1.2.0 // indirect github.com/yalue/merged_fs v1.3.0 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/net v0.34.0 // indirect + golang.org/x/net v0.35.0 // indirect golang.org/x/oauth2 v0.24.0 // indirect golang.org/x/sync v0.11.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/text v0.22.0 // indirect golang.org/x/tools v0.29.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/pkg/plugins/codegen/go.sum b/pkg/plugins/codegen/go.sum index d8efc875fc9..a940ced9650 100644 --- a/pkg/plugins/codegen/go.sum +++ b/pkg/plugins/codegen/go.sum @@ -94,16 +94,16 @@ github.com/yalue/merged_fs v1.3.0 h1:qCeh9tMPNy/i8cwDsQTJ5bLr6IRxbs6meakNE5O+wyY github.com/yalue/merged_fs v1.3.0/go.mod h1:WqqchfVYQyclV2tnR7wtRhBddzBvLVR83Cjw9BKQw0M= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index 3d743ca89bd..95380de2939 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -107,12 +107,14 @@ require ( go.opentelemetry.io/otel/sdk v1.34.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/atomic v1.11.0 // indirect + golang.org/x/crypto v0.35.0 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/net v0.34.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/text v0.22.0 // indirect golang.org/x/tools v0.29.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect google.golang.org/api v0.216.0 // indirect diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index 9f9b18c82b0..66a0da7e790 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -329,8 +329,8 @@ go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -342,10 +342,10 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= -golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -367,8 +367,8 @@ golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/pkg/registry/apis/folders/legacy_storage.go b/pkg/registry/apis/folders/legacy_storage.go index f85b282979b..0c96769a942 100644 --- a/pkg/registry/apis/folders/legacy_storage.go +++ b/pkg/registry/apis/folders/legacy_storage.go @@ -142,7 +142,8 @@ func (s *legacyStorage) Get(ctx context.Context, name string, options *metav1.Ge if errors.Is(err, dashboards.ErrFolderNotFound) || err == nil { err = resourceInfo.NewNotFound(name) } - return nil, err + statusErr := apierrors.ToFolderStatusError(err) + return nil, &statusErr } r, err := convertToK8sResource(dto, s.namespacer) diff --git a/pkg/registry/apps/advisor/register.go b/pkg/registry/apps/advisor/register.go index 352e364eff0..e825f392265 100644 --- a/pkg/registry/apps/advisor/register.go +++ b/pkg/registry/apps/advisor/register.go @@ -24,6 +24,7 @@ func RegisterApp( specificConfig := checkregistry.AdvisorAppConfig{ CheckRegistry: checkRegistry, PluginConfig: pluginConfig, + StackID: cfg.StackID, } appCfg := &runner.AppBuilderConfig{ OpenAPIDefGetter: advisorv0alpha1.GetOpenAPIDefinitions, diff --git a/pkg/services/ngalert/api/api.go b/pkg/services/ngalert/api/api.go index 8973ca6cce7..10d50a6b0b5 100644 --- a/pkg/services/ngalert/api/api.go +++ b/pkg/services/ngalert/api/api.go @@ -187,8 +187,8 @@ func (api *API) RegisterAPIEndpoints(m *metrics.API) { }), m) if api.FeatureManager.IsEnabledGlobally(featuremgmt.FlagAlertingConversionAPI) { - api.RegisterConvertPrometheusApiEndpoints(NewConvertPrometheusApi(&ConvertPrometheusSrv{ - logger: logger, - }), m) + api.RegisterConvertPrometheusApiEndpoints(NewConvertPrometheusApi( + NewConvertPrometheusSrv(&api.Cfg.UnifiedAlerting, logger, api.RuleStore, api.DatasourceCache, api.AlertRules), + ), m) } } diff --git a/pkg/services/ngalert/api/api_convert_prometheus.go b/pkg/services/ngalert/api/api_convert_prometheus.go index babe4f1bca4..c27bf9b118a 100644 --- a/pkg/services/ngalert/api/api_convert_prometheus.go +++ b/pkg/services/ngalert/api/api_convert_prometheus.go @@ -1,14 +1,60 @@ package api import ( + "fmt" + "net/http" + "strconv" + "strings" + "github.com/grafana/grafana/pkg/api/response" + "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/infra/log" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" + "github.com/grafana/grafana/pkg/services/datasources" + "github.com/grafana/grafana/pkg/services/folder" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" + "github.com/grafana/grafana/pkg/services/ngalert/models" + "github.com/grafana/grafana/pkg/services/ngalert/prom" + "github.com/grafana/grafana/pkg/services/ngalert/provisioning" + "github.com/grafana/grafana/pkg/setting" ) +const ( + datasourceUIDHeader = "X-Grafana-Alerting-Datasource-UID" + recordingRulesPausedHeader = "X-Grafana-Alerting-Recording-Rules-Paused" + alertRulesPausedHeader = "X-Grafana-Alerting-Alert-Rules-Paused" +) + +var ( + errDatasourceUIDHeaderMissing = errutil.ValidationFailed( + "alerting.datasourceUIDHeaderMissing", + errutil.WithPublicMessage(fmt.Sprintf("Missing datasource UID header: %s", datasourceUIDHeader)), + ).Errorf("missing datasource UID header") + + errInvalidHeaderValueMsg = "Invalid value for header {{.Public.Header}}: must be 'true' or 'false'" + errInvalidHeaderValueBase = errutil.ValidationFailed("aleting.invalidHeaderValue").MustTemplate(errInvalidHeaderValueMsg, errutil.WithPublic(errInvalidHeaderValueMsg)) +) + +func errInvalidHeaderValue(header string) error { + return errInvalidHeaderValueBase.Build(errutil.TemplateData{Public: map[string]any{"Header": header}}) +} + type ConvertPrometheusSrv struct { - logger log.Logger + cfg *setting.UnifiedAlertingSettings + logger log.Logger + ruleStore RuleStore + datasourceCache datasources.CacheService + alertRuleService *provisioning.AlertRuleService +} + +func NewConvertPrometheusSrv(cfg *setting.UnifiedAlertingSettings, logger log.Logger, ruleStore RuleStore, datasourceCache datasources.CacheService, alertRuleService *provisioning.AlertRuleService) *ConvertPrometheusSrv { + return &ConvertPrometheusSrv{ + cfg: cfg, + logger: logger, + ruleStore: ruleStore, + datasourceCache: datasourceCache, + alertRuleService: alertRuleService, + } } func (srv *ConvertPrometheusSrv) RouteConvertPrometheusGetRules(c *contextmodel.ReqContext) response.Response { @@ -28,9 +74,131 @@ func (srv *ConvertPrometheusSrv) RouteConvertPrometheusGetNamespace(c *contextmo } func (srv *ConvertPrometheusSrv) RouteConvertPrometheusGetRuleGroup(c *contextmodel.ReqContext, namespaceTitle string, group string) response.Response { - return response.Error(501, "Not implemented", nil) + // Just to make the mimirtool rules load work. It first checks if the group exists, and if the endpoint returns 501 it fails. + return response.YAML(http.StatusOK, apimodels.PrometheusRuleGroup{}) } -func (srv *ConvertPrometheusSrv) RouteConvertPrometheusPostRuleGroup(c *contextmodel.ReqContext, namespaceTitle string, prometheusGroup apimodels.PrometheusRuleGroup) response.Response { - return response.Error(501, "Not implemented", nil) +func (srv *ConvertPrometheusSrv) RouteConvertPrometheusPostRuleGroup(c *contextmodel.ReqContext, namespaceTitle string, promGroup apimodels.PrometheusRuleGroup) response.Response { + logger := srv.logger.FromContext(c.Req.Context()) + logger = logger.New("folder_title", namespaceTitle, "group", promGroup.Name) + + logger.Info("Converting Prometheus rule group", "rules", len(promGroup.Rules)) + + ns, errResp := srv.getOrCreateNamespace(c, namespaceTitle, logger) + if errResp != nil { + return errResp + } + + datasourceUID := strings.TrimSpace(c.Req.Header.Get(datasourceUIDHeader)) + if datasourceUID == "" { + return response.Err(errDatasourceUIDHeaderMissing) + } + ds, err := srv.datasourceCache.GetDatasourceByUID(c.Req.Context(), datasourceUID, c.SignedInUser, c.SkipDSCache) + if err != nil { + logger.Error("Failed to get datasource", "datasource_uid", datasourceUID, "error", err) + return errorToResponse(err) + } + + group, err := srv.convertToGrafanaRuleGroup(c, ds, ns.UID, promGroup, logger) + if err != nil { + return errorToResponse(err) + } + + err = srv.alertRuleService.ReplaceRuleGroup(c.Req.Context(), c.SignedInUser, *group, models.ProvenanceConvertedPrometheus) + if err != nil { + logger.Error("Failed to replace rule group", "error", err) + return errorToResponse(err) + } + + return response.JSON(http.StatusAccepted, map[string]string{"status": "success"}) +} + +func (srv *ConvertPrometheusSrv) getOrCreateNamespace(c *contextmodel.ReqContext, title string, logger log.Logger) (*folder.Folder, response.Response) { + logger.Debug("Getting or creating a new folder") + + ns, err := srv.ruleStore.GetOrCreateNamespaceInRootByTitle( + c.Req.Context(), + title, + c.SignedInUser.GetOrgID(), + c.SignedInUser, + ) + if err != nil { + logger.Error("Failed to get or create a new folder", "error", err) + return nil, toNamespaceErrorResponse(err) + } + + logger.Debug("Using folder for the converted rules", "folder_uid", ns.UID) + + return ns, nil +} + +func (srv *ConvertPrometheusSrv) convertToGrafanaRuleGroup(c *contextmodel.ReqContext, ds *datasources.DataSource, namespaceUID string, promGroup apimodels.PrometheusRuleGroup, logger log.Logger) (*models.AlertRuleGroup, error) { + logger.Info("Converting Prometheus rules to Grafana rules", "rules", len(promGroup.Rules), "folder_uid", namespaceUID, "datasource_uid", ds.UID, "datasource_type", ds.Type) + + rules := make([]prom.PrometheusRule, len(promGroup.Rules)) + for i, r := range promGroup.Rules { + rules[i] = prom.PrometheusRule{ + Alert: r.Alert, + Expr: r.Expr, + For: r.For, + KeepFiringFor: r.KeepFiringFor, + Labels: r.Labels, + Annotations: r.Annotations, + Record: r.Record, + } + } + group := prom.PrometheusRuleGroup{ + Name: promGroup.Name, + Interval: promGroup.Interval, + Rules: rules, + } + + pauseRecordingRules, err := parseBooleanHeader(c.Req.Header.Get(recordingRulesPausedHeader), recordingRulesPausedHeader) + if err != nil { + return nil, err + } + + pauseAlertRules, err := parseBooleanHeader(c.Req.Header.Get(alertRulesPausedHeader), alertRulesPausedHeader) + if err != nil { + return nil, err + } + + converter, err := prom.NewConverter( + prom.Config{ + DatasourceUID: ds.UID, + DatasourceType: ds.Type, + DefaultInterval: srv.cfg.DefaultRuleEvaluationInterval, + RecordingRules: prom.RulesConfig{ + IsPaused: pauseRecordingRules, + }, + AlertRules: prom.RulesConfig{ + IsPaused: pauseAlertRules, + }, + }, + ) + if err != nil { + logger.Error("Failed to create Prometheus converter", "datasource_uid", ds.UID, "datasource_type", ds.Type, "error", err) + return nil, err + } + + grafanaGroup, err := converter.PrometheusRulesToGrafana(c.SignedInUser.GetOrgID(), namespaceUID, group) + if err != nil { + logger.Error("Failed to convert Prometheus rules to Grafana rules", "error", err) + return nil, err + } + + return grafanaGroup, nil +} + +// parseBooleanHeader parses a boolean header value, returning an error if the header +// is present but invalid. If the header is not present, returns (false, nil). +func parseBooleanHeader(header string, headerName string) (bool, error) { + if header == "" { + return false, nil + } + val, err := strconv.ParseBool(header) + if err != nil { + return false, errInvalidHeaderValue(headerName) + } + return val, nil } diff --git a/pkg/services/ngalert/api/api_convert_prometheus_test.go b/pkg/services/ngalert/api/api_convert_prometheus_test.go new file mode 100644 index 00000000000..8dea6f3f178 --- /dev/null +++ b/pkg/services/ngalert/api/api_convert_prometheus_test.go @@ -0,0 +1,212 @@ +package api + +import ( + "net/http" + "net/http/httptest" + "testing" + "time" + + prommodel "github.com/prometheus/common/model" + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/infra/log" + contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" + "github.com/grafana/grafana/pkg/services/datasources" + dsfakes "github.com/grafana/grafana/pkg/services/datasources/fakes" + "github.com/grafana/grafana/pkg/services/folder/foldertest" + acfakes "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol/fakes" + apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" + "github.com/grafana/grafana/pkg/services/ngalert/provisioning" + "github.com/grafana/grafana/pkg/services/ngalert/tests/fakes" + "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" + "github.com/grafana/grafana/pkg/web" +) + +const ( + existingDSUID = "test-ds" +) + +func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) { + simpleGroup := apimodels.PrometheusRuleGroup{ + Name: "Test Group", + Interval: prommodel.Duration(1 * time.Minute), + Rules: []apimodels.PrometheusRule{ + { + Alert: "TestAlert", + Expr: "up == 0", + For: util.Pointer(prommodel.Duration(5 * time.Minute)), + Labels: map[string]string{ + "severity": "critical", + }, + }, + }, + } + + t.Run("without datasource UID header should return 400", func(t *testing.T) { + srv, _ := createConvertPrometheusSrv(t) + rc := createRequestCtx() + rc.Req.Header.Set(datasourceUIDHeader, "") + + response := srv.RouteConvertPrometheusPostRuleGroup(rc, "test", apimodels.PrometheusRuleGroup{}) + + require.Equal(t, http.StatusBadRequest, response.Status()) + require.Contains(t, string(response.Body()), "Missing datasource UID header") + }) + + t.Run("with invalid datasource should return error", func(t *testing.T) { + srv, _ := createConvertPrometheusSrv(t) + rc := createRequestCtx() + rc.Req.Header.Set(datasourceUIDHeader, "non-existing-ds") + + response := srv.RouteConvertPrometheusPostRuleGroup(rc, "test", apimodels.PrometheusRuleGroup{}) + + require.Equal(t, http.StatusNotFound, response.Status()) + }) + + t.Run("with rule group without evaluation interval should return 202", func(t *testing.T) { + srv, _ := createConvertPrometheusSrv(t) + rc := createRequestCtx() + + response := srv.RouteConvertPrometheusPostRuleGroup(rc, "test", simpleGroup) + require.Equal(t, http.StatusAccepted, response.Status()) + }) + + t.Run("with valid pause header values should return 202", func(t *testing.T) { + testCases := []struct { + name string + headerName string + headerValue string + }{ + { + name: "true recording rules pause value", + headerName: recordingRulesPausedHeader, + headerValue: "true", + }, + { + name: "false recording rules pause value", + headerName: recordingRulesPausedHeader, + headerValue: "false", + }, + { + name: "true alert rules pause value", + headerName: alertRulesPausedHeader, + headerValue: "true", + }, + { + name: "false alert rules pause value", + headerName: alertRulesPausedHeader, + headerValue: "false", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + srv, _ := createConvertPrometheusSrv(t) + rc := createRequestCtx() + rc.Req.Header.Set(tc.headerName, tc.headerValue) + + response := srv.RouteConvertPrometheusPostRuleGroup(rc, "test", simpleGroup) + require.Equal(t, http.StatusAccepted, response.Status()) + }) + } + }) + + t.Run("with invalid pause header values should return 400", func(t *testing.T) { + testCases := []struct { + name string + headerName string + headerValue string + expectedError string + }{ + { + name: "invalid recording rules pause value", + headerName: recordingRulesPausedHeader, + headerValue: "invalid", + expectedError: "Invalid value for header X-Grafana-Alerting-Recording-Rules-Paused: must be 'true' or 'false'", + }, + { + name: "invalid alert rules pause value", + headerName: alertRulesPausedHeader, + headerValue: "invalid", + expectedError: "Invalid value for header X-Grafana-Alerting-Alert-Rules-Paused: must be 'true' or 'false'", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + srv, _ := createConvertPrometheusSrv(t) + rc := createRequestCtx() + rc.Req.Header.Set(tc.headerName, tc.headerValue) + + response := srv.RouteConvertPrometheusPostRuleGroup(rc, "test", simpleGroup) + require.Equal(t, http.StatusBadRequest, response.Status()) + require.Contains(t, string(response.Body()), tc.expectedError) + }) + } + }) + + t.Run("with valid request should return 202", func(t *testing.T) { + srv, _ := createConvertPrometheusSrv(t) + rc := createRequestCtx() + + response := srv.RouteConvertPrometheusPostRuleGroup(rc, "test", simpleGroup) + require.Equal(t, http.StatusAccepted, response.Status()) + }) +} + +func createConvertPrometheusSrv(t *testing.T) (*ConvertPrometheusSrv, datasources.CacheService) { + t.Helper() + + ruleStore := fakes.NewRuleStore(t) + folder := randFolder() + ruleStore.Folders[1] = append(ruleStore.Folders[1], folder) + + dsCache := &dsfakes.FakeCacheService{} + ds := &datasources.DataSource{ + UID: existingDSUID, + Type: datasources.DS_PROMETHEUS, + } + dsCache.DataSources = append(dsCache.DataSources, ds) + + quotas := &provisioning.MockQuotaChecker{} + quotas.EXPECT().LimitOK() + + folderService := foldertest.NewFakeService() + + alertRuleService := provisioning.NewAlertRuleService( + ruleStore, + fakes.NewFakeProvisioningStore(), + folderService, + quotas, + &provisioning.NopTransactionManager{}, + 60, + 10, + 100, + log.New("test"), + &provisioning.NotificationSettingsValidatorProviderFake{}, + &acfakes.FakeRuleService{}, + ) + + cfg := &setting.UnifiedAlertingSettings{ + DefaultRuleEvaluationInterval: 1 * time.Minute, + } + + srv := NewConvertPrometheusSrv(cfg, log.NewNopLogger(), ruleStore, dsCache, alertRuleService) + + return srv, dsCache +} + +func createRequestCtx() *contextmodel.ReqContext { + req := httptest.NewRequest("GET", "http://localhost", nil) + req.Header.Set(datasourceUIDHeader, existingDSUID) + + return &contextmodel.ReqContext{ + Context: &web.Context{ + Req: req, + Resp: web.NewResponseWriter("GET", httptest.NewRecorder()), + }, + SignedInUser: &user.SignedInUser{OrgID: 1}, + } +} diff --git a/pkg/services/ngalert/api/authorization.go b/pkg/services/ngalert/api/authorization.go index 155f3e0131e..a1cedae4b73 100644 --- a/pkg/services/ngalert/api/authorization.go +++ b/pkg/services/ngalert/api/authorization.go @@ -132,23 +132,26 @@ func (api *API) authorize(method, path string) web.Handler { ) case http.MethodGet + "/api/convert/prometheus/config/v1/rules": - eval = ac.EvalPermission(ac.ActionAlertingRuleRead) + eval = ac.EvalAll( + ac.EvalPermission(ac.ActionAlertingRuleRead), + ac.EvalPermission(dashboards.ActionFoldersRead), + ) case http.MethodPost + "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}": eval = ac.EvalAll( - ac.EvalPermission(dashboards.ActionFoldersWrite), - ac.EvalPermission(ac.ActionAlertingRuleRead), - ac.EvalPermission(ac.ActionAlertingRuleUpdate), ac.EvalPermission(ac.ActionAlertingRuleCreate), - ac.EvalPermission(ac.ActionAlertingRuleDelete), + ac.EvalPermission(ac.ActionAlertingProvisioningSetStatus), ) case http.MethodDelete + "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}/{Group}", http.MethodDelete + "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}": - eval = ac.EvalAll( - ac.EvalPermission(ac.ActionAlertingRuleDelete), - ac.EvalPermission(ac.ActionAlertingRuleRead), - ac.EvalPermission(dashboards.ActionFoldersRead), + eval = ac.EvalAny( + ac.EvalAll( + ac.EvalPermission(ac.ActionAlertingRuleRead), + ac.EvalPermission(dashboards.ActionFoldersRead), + ac.EvalPermission(ac.ActionAlertingRuleDelete), + ac.EvalPermission(ac.ActionAlertingProvisioningSetStatus), + ), ) // Alert Instances and Silences diff --git a/pkg/services/ngalert/api/persist.go b/pkg/services/ngalert/api/persist.go index 952db94f1f5..57eda01631d 100644 --- a/pkg/services/ngalert/api/persist.go +++ b/pkg/services/ngalert/api/persist.go @@ -15,6 +15,8 @@ type RuleStore interface { // by returning map[string]struct{} instead of map[string]*folder.Folder GetUserVisibleNamespaces(context.Context, int64, identity.Requester) (map[string]*folder.Folder, error) GetNamespaceByUID(ctx context.Context, uid string, orgID int64, user identity.Requester) (*folder.Folder, error) + GetNamespaceInRootByTitle(ctx context.Context, fullpath string, orgID int64, user identity.Requester) (*folder.Folder, error) + GetOrCreateNamespaceInRootByTitle(ctx context.Context, title string, orgID int64, user identity.Requester) (*folder.Folder, error) GetAlertRuleByUID(ctx context.Context, query *ngmodels.GetAlertRuleByUIDQuery) (*ngmodels.AlertRule, error) GetAlertRulesGroupByRuleUID(ctx context.Context, query *ngmodels.GetAlertRulesGroupByRuleUIDQuery) ([]*ngmodels.AlertRule, error) diff --git a/pkg/services/ngalert/api/tooling/api.json b/pkg/services/ngalert/api/tooling/api.json index 7ed5ae04ba0..0a2fb0af0d4 100644 --- a/pkg/services/ngalert/api/tooling/api.json +++ b/pkg/services/ngalert/api/tooling/api.json @@ -4493,6 +4493,7 @@ "type": "object" }, "URL": { + "description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nThe Host field contains the host and port subcomponents of the URL.\nWhen the port is present, it is separated from the host with a colon.\nWhen the host is an IPv6 address, it must be enclosed in square brackets:\n\"[fe80::1]:80\". The [net.JoinHostPort] function combines a host and port\ninto a string suitable for the Host field, adding square brackets to\nthe host when necessary.\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use the [URL.EscapedPath] method, which preserves\nthe original encoding of Path.\n\nThe RawPath field is an optional field which is only set when the default\nencoding of Path is different from the escaped path. See the EscapedPath method\nfor more details.\n\nURL's String method uses the EscapedPath method to obtain the path.", "properties": { "ForceQuery": { "type": "boolean" @@ -4528,7 +4529,7 @@ "$ref": "#/definitions/Userinfo" } }, - "title": "URL is a custom URL type that allows validation at configuration load time.", + "title": "A URL represents a parsed URL (technically, a URI reference).", "type": "object" }, "UpdateRuleGroupResponse": { @@ -4931,7 +4932,6 @@ "type": "object" }, "gettableAlerts": { - "description": "GettableAlerts gettable alerts", "items": { "$ref": "#/definitions/gettableAlert", "type": "object" @@ -5056,7 +5056,6 @@ "type": "object" }, "gettableSilences": { - "description": "GettableSilences gettable silences", "items": { "$ref": "#/definitions/gettableSilence", "type": "object" diff --git a/pkg/services/ngalert/api/tooling/definitions/contact_points.go b/pkg/services/ngalert/api/tooling/definitions/contact_points.go index 498ac6f0d37..597694e6d60 100644 --- a/pkg/services/ngalert/api/tooling/definitions/contact_points.go +++ b/pkg/services/ngalert/api/tooling/definitions/contact_points.go @@ -313,15 +313,26 @@ type WebhookIntegration struct { URL string `json:"url" yaml:"url" hcl:"url"` - HTTPMethod *string `json:"httpMethod,omitempty" yaml:"httpMethod,omitempty" hcl:"http_method"` - MaxAlerts *int64 `json:"maxAlerts,omitempty" yaml:"maxAlerts,omitempty" hcl:"max_alerts"` - AuthorizationScheme *string `json:"authorization_scheme,omitempty" yaml:"authorization_scheme,omitempty" hcl:"authorization_scheme"` - AuthorizationCredentials *Secret `json:"authorization_credentials,omitempty" yaml:"authorization_credentials,omitempty" hcl:"authorization_credentials"` - User *string `json:"username,omitempty" yaml:"username,omitempty" hcl:"basic_auth_user"` - Password *Secret `json:"password,omitempty" yaml:"password,omitempty" hcl:"basic_auth_password"` - Title *string `json:"title,omitempty" yaml:"title,omitempty" hcl:"title"` - Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"` - TLSConfig *TLSConfig `json:"tlsConfig,omitempty" yaml:"tlsConfig,omitempty" hcl:"tlsConfig,block"` + HTTPMethod *string `json:"httpMethod,omitempty" yaml:"httpMethod,omitempty" hcl:"http_method"` + MaxAlerts *int64 `json:"maxAlerts,omitempty" yaml:"maxAlerts,omitempty" hcl:"max_alerts"` + AuthorizationScheme *string `json:"authorization_scheme,omitempty" yaml:"authorization_scheme,omitempty" hcl:"authorization_scheme"` + AuthorizationCredentials *Secret `json:"authorization_credentials,omitempty" yaml:"authorization_credentials,omitempty" hcl:"authorization_credentials"` + User *string `json:"username,omitempty" yaml:"username,omitempty" hcl:"basic_auth_user"` + Password *Secret `json:"password,omitempty" yaml:"password,omitempty" hcl:"basic_auth_password"` + Title *string `json:"title,omitempty" yaml:"title,omitempty" hcl:"title"` + Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"` + TLSConfig *TLSConfig `json:"tlsConfig,omitempty" yaml:"tlsConfig,omitempty" hcl:"tlsConfig,block"` + HMACConfig *HMACConfig `json:"hmacConfig,omitempty" yaml:"hmacConfig,omitempty" hcl:"hmacConfig,block"` +} + +type HMACConfig struct { + // Secret to use for HMAC signing. + Secret *Secret `json:"secret,omitempty" yaml:"secret,omitempty" hcl:"secret"` + // Header is the name of the header containing the HMAC signature. + Header string `json:"header,omitempty" yaml:"header,omitempty" hcl:"header"` + // TimestampHeader is the name of the header containing the timestamp + // used to generate the HMAC signature. If empty, timestamp is not included. + TimestampHeader string `yaml:"timestampHeader,omitempty" json:"timestampHeader,omitempty" hcl:"timestamp_header"` } type WecomIntegration struct { diff --git a/pkg/services/ngalert/api/tooling/definitions/convert_prometheus_api.go b/pkg/services/ngalert/api/tooling/definitions/convert_prometheus_api.go index b2bae42eacb..5b6454d1d84 100644 --- a/pkg/services/ngalert/api/tooling/definitions/convert_prometheus_api.go +++ b/pkg/services/ngalert/api/tooling/definitions/convert_prometheus_api.go @@ -84,11 +84,11 @@ type RouteConvertPrometheusPostRuleGroupParams struct { // in: path NamespaceTitle string // in: header - DatasourceUID string `json:"x-datasource-uid"` + DatasourceUID string `json:"x-grafana-alerting-datasource-uid"` // in: header - RecordingRulesPaused bool `json:"x-recording-rules-paused"` + RecordingRulesPaused bool `json:"x-grafana-alerting-recording-rules-paused"` // in: header - AlertRulesPaused bool `json:"x-alert-rules-paused"` + AlertRulesPaused bool `json:"x-grafana-alerting-alert-rules-paused"` // in:body Body PrometheusRuleGroup } diff --git a/pkg/services/ngalert/api/tooling/post.json b/pkg/services/ngalert/api/tooling/post.json index 26e062391cf..825af6c52af 100644 --- a/pkg/services/ngalert/api/tooling/post.json +++ b/pkg/services/ngalert/api/tooling/post.json @@ -4932,6 +4932,7 @@ "type": "object" }, "gettableAlerts": { + "description": "GettableAlerts gettable alerts", "items": { "$ref": "#/definitions/gettableAlert", "type": "object" @@ -6515,17 +6516,17 @@ }, { "in": "header", - "name": "x-datasource-uid", + "name": "x-grafana-alerting-datasource-uid", "type": "string" }, { "in": "header", - "name": "x-recording-rules-paused", + "name": "x-grafana-alerting-recording-rules-paused", "type": "boolean" }, { "in": "header", - "name": "x-alert-rules-paused", + "name": "x-grafana-alerting-alert-rules-paused", "type": "boolean" }, { diff --git a/pkg/services/ngalert/api/tooling/spec.json b/pkg/services/ngalert/api/tooling/spec.json index f42d1f7c408..e8f3f798fda 100644 --- a/pkg/services/ngalert/api/tooling/spec.json +++ b/pkg/services/ngalert/api/tooling/spec.json @@ -1194,17 +1194,17 @@ }, { "type": "string", - "name": "x-datasource-uid", + "name": "x-grafana-alerting-datasource-uid", "in": "header" }, { "type": "boolean", - "name": "x-recording-rules-paused", + "name": "x-grafana-alerting-recording-rules-paused", "in": "header" }, { "type": "boolean", - "name": "x-alert-rules-paused", + "name": "x-grafana-alerting-alert-rules-paused", "in": "header" }, { @@ -8872,6 +8872,7 @@ } }, "gettableAlerts": { + "description": "GettableAlerts gettable alerts", "type": "array", "items": { "type": "object", diff --git a/pkg/services/ngalert/models/provisioning.go b/pkg/services/ngalert/models/provisioning.go index 7933830588b..d792e821939 100644 --- a/pkg/services/ngalert/models/provisioning.go +++ b/pkg/services/ngalert/models/provisioning.go @@ -8,10 +8,12 @@ const ( ProvenanceNone Provenance = "" ProvenanceAPI Provenance = "api" ProvenanceFile Provenance = "file" + // ProvenanceConvertedPrometheus is used for objects converted from Prometheus definitions. + ProvenanceConvertedPrometheus Provenance = "converted_prometheus" ) var ( - KnownProvenances = []Provenance{ProvenanceNone, ProvenanceAPI, ProvenanceFile} + KnownProvenances = []Provenance{ProvenanceNone, ProvenanceAPI, ProvenanceFile, ProvenanceConvertedPrometheus} ) // Provisionable represents a resource that can be created through a provisioning mechanism, such as Terraform or config file. diff --git a/pkg/services/ngalert/notifier/channels_config/available_channels.go b/pkg/services/ngalert/notifier/channels_config/available_channels.go index a64c641a40e..2e2ce80d24d 100644 --- a/pkg/services/ngalert/notifier/channels_config/available_channels.go +++ b/pkg/services/ngalert/notifier/channels_config/available_channels.go @@ -1689,13 +1689,13 @@ func GetAvailableNotifiers() []*NotifierPlugin { Label: "Subject", Element: ElementTypeTextArea, InputType: InputTypeText, - Description: "Optional subject. You can use templates to customize this field", + Description: "Optional subject. By default, this field uses the default title template and can be customized with templates and custom messages. It cannot be an empty string", PropertyName: "subject", Placeholder: alertingTemplates.DefaultMessageTitleEmbed, }, { Label: "Message", - Description: "Optional message. You can use templates to customize this field. Using a custom message will replace the default message", + Description: "Optional message. By default, this field uses the default message template and can be customized with templates and custom messages", Element: ElementTypeTextArea, PropertyName: "message", Placeholder: alertingTemplates.DefaultMessageEmbed, diff --git a/pkg/services/ngalert/prom/convert.go b/pkg/services/ngalert/prom/convert.go index bb9d4c3d616..e2f46b74341 100644 --- a/pkg/services/ngalert/prom/convert.go +++ b/pkg/services/ngalert/prom/convert.go @@ -27,8 +27,11 @@ const ( // Config defines the configuration options for the Prometheus to Grafana rules converter. type Config struct { - DatasourceUID string - DatasourceType string + DatasourceUID string + DatasourceType string + // DefaultInterval is the default interval for rules in the groups that + // don't have Interval set. + DefaultInterval time.Duration FromTimeRange *time.Duration EvaluationOffset *time.Duration ExecErrState models.ExecutionErrorState @@ -68,6 +71,9 @@ func NewConverter(cfg Config) (*Converter, error) { if cfg.DatasourceType == "" { return nil, fmt.Errorf("datasource type is required") } + if cfg.DefaultInterval == 0 { + return nil, fmt.Errorf("default evaluation interval is required") + } if cfg.FromTimeRange == nil { cfg.FromTimeRange = defaultConfig.FromTimeRange } @@ -93,9 +99,8 @@ func NewConverter(cfg Config) (*Converter, error) { // PrometheusRulesToGrafana converts a Prometheus rule group into Grafana Alerting rule group. func (p *Converter) PrometheusRulesToGrafana(orgID int64, namespaceUID string, group PrometheusRuleGroup) (*models.AlertRuleGroup, error) { for _, rule := range group.Rules { - err := validatePrometheusRule(rule) - if err != nil { - return nil, fmt.Errorf("invalid Prometheus rule '%s': %w", rule.Alert, err) + if err := rule.Validate(); err != nil { + return nil, err } } @@ -107,18 +112,15 @@ func (p *Converter) PrometheusRulesToGrafana(orgID int64, namespaceUID string, g return grafanaGroup, nil } -func validatePrometheusRule(rule PrometheusRule) error { - if rule.KeepFiringFor != nil { - return fmt.Errorf("keep_firing_for is not supported") - } - - return nil -} - func (p *Converter) convertRuleGroup(orgID int64, namespaceUID string, promGroup PrometheusRuleGroup) (*models.AlertRuleGroup, error) { uniqueNames := map[string]int{} rules := make([]models.AlertRule, 0, len(promGroup.Rules)) + interval := time.Duration(promGroup.Interval) + if interval == 0 { + interval = p.cfg.DefaultInterval + } + for i, rule := range promGroup.Rules { gr, err := p.convertRule(orgID, namespaceUID, promGroup.Name, rule) if err != nil { diff --git a/pkg/services/ngalert/prom/convert_test.go b/pkg/services/ngalert/prom/convert_test.go index 3bad4795fcb..332cb356804 100644 --- a/pkg/services/ngalert/prom/convert_test.go +++ b/pkg/services/ngalert/prom/convert_test.go @@ -19,7 +19,7 @@ import ( ) func TestPrometheusRulesToGrafana(t *testing.T) { - fiveMin := prommodel.Duration(5 * time.Minute) + defaultInterval := 2 * time.Minute testCases := []struct { name string @@ -40,7 +40,7 @@ func TestPrometheusRulesToGrafana(t *testing.T) { { Alert: "alert-1", Expr: "cpu_usage > 80", - For: &fiveMin, + For: util.Pointer(prommodel.Duration(5 * time.Minute)), Labels: map[string]string{ "severity": "critical", }, @@ -63,14 +63,14 @@ func TestPrometheusRulesToGrafana(t *testing.T) { { Alert: "alert-1", Expr: "up == 0", - KeepFiringFor: &fiveMin, + KeepFiringFor: util.Pointer(prommodel.Duration(5 * time.Minute)), }, }, }, expectError: true, }, { - name: "rule with empty interval", + name: "rule group with empty interval", orgID: 1, namespace: "namespaceUID", promGroup: PrometheusRuleGroup{ @@ -89,7 +89,8 @@ func TestPrometheusRulesToGrafana(t *testing.T) { orgID: 1, namespace: "namespaceUID", promGroup: PrometheusRuleGroup{ - Name: "test-group-1", + Name: "test-group-1", + Interval: prommodel.Duration(10 * time.Second), Rules: []PrometheusRule{ { Record: "some_metric", @@ -105,6 +106,7 @@ func TestPrometheusRulesToGrafana(t *testing.T) { t.Run(tc.name, func(t *testing.T) { tc.config.DatasourceUID = "datasource-uid" tc.config.DatasourceType = datasources.DS_PROMETHEUS + tc.config.DefaultInterval = defaultInterval converter, err := NewConverter(tc.config) require.NoError(t, err) @@ -117,7 +119,11 @@ func TestPrometheusRulesToGrafana(t *testing.T) { require.NoError(t, err, tc.name) require.Equal(t, tc.promGroup.Name, grafanaGroup.Title, tc.name) + expectedInterval := int64(time.Duration(tc.promGroup.Interval).Seconds()) + if expectedInterval == 0 { + expectedInterval = int64(defaultInterval.Seconds()) + } require.Equal(t, expectedInterval, grafanaGroup.Interval, tc.name) require.Equal(t, len(tc.promGroup.Rules), len(grafanaGroup.Rules), tc.name) @@ -164,8 +170,9 @@ func TestPrometheusRulesToGrafana(t *testing.T) { func TestPrometheusRulesToGrafanaWithDuplicateRuleNames(t *testing.T) { cfg := Config{ - DatasourceUID: "datasource-uid", - DatasourceType: datasources.DS_PROMETHEUS, + DatasourceUID: "datasource-uid", + DatasourceType: datasources.DS_PROMETHEUS, + DefaultInterval: 2 * time.Minute, } converter, err := NewConverter(cfg) require.NoError(t, err) @@ -257,8 +264,9 @@ func TestCreateThresholdNode(t *testing.T) { func TestPrometheusRulesToGrafana_NodesInRules(t *testing.T) { cfg := Config{ - DatasourceUID: "datasource-uid", - DatasourceType: datasources.DS_PROMETHEUS, + DatasourceUID: "datasource-uid", + DatasourceType: datasources.DS_PROMETHEUS, + DefaultInterval: 2 * time.Minute, } converter, err := NewConverter(cfg) require.NoError(t, err) @@ -344,8 +352,9 @@ func TestPrometheusRulesToGrafana_UID(t *testing.T) { } converter, err := NewConverter(Config{ - DatasourceUID: "datasource-uid", - DatasourceType: datasources.DS_PROMETHEUS, + DatasourceUID: "datasource-uid", + DatasourceType: datasources.DS_PROMETHEUS, + DefaultInterval: 2 * time.Minute, }) require.NoError(t, err) @@ -372,8 +381,9 @@ func TestPrometheusRulesToGrafana_UID(t *testing.T) { namespace := "some-namespace" converter, err := NewConverter(Config{ - DatasourceUID: "datasource-uid", - DatasourceType: datasources.DS_PROMETHEUS, + DatasourceUID: "datasource-uid", + DatasourceType: datasources.DS_PROMETHEUS, + DefaultInterval: 2 * time.Minute, }) require.NoError(t, err) @@ -390,8 +400,9 @@ func TestPrometheusRulesToGrafana_UID(t *testing.T) { namespace := "some-namespace" converter, err := NewConverter(Config{ - DatasourceUID: "datasource-uid", - DatasourceType: datasources.DS_PROMETHEUS, + DatasourceUID: "datasource-uid", + DatasourceType: datasources.DS_PROMETHEUS, + DefaultInterval: 2 * time.Minute, }) require.NoError(t, err) @@ -408,8 +419,9 @@ func TestPrometheusRulesToGrafana_UID(t *testing.T) { namespace := "some-namespace" converter, err := NewConverter(Config{ - DatasourceUID: "datasource-uid", - DatasourceType: datasources.DS_PROMETHEUS, + DatasourceUID: "datasource-uid", + DatasourceType: datasources.DS_PROMETHEUS, + DefaultInterval: 2 * time.Minute, }) require.NoError(t, err) diff --git a/pkg/services/ngalert/prom/models.go b/pkg/services/ngalert/prom/models.go index f7e8bbfc95b..cbf57b2015c 100644 --- a/pkg/services/ngalert/prom/models.go +++ b/pkg/services/ngalert/prom/models.go @@ -2,6 +2,12 @@ package prom import ( prommodel "github.com/prometheus/common/model" + + "github.com/grafana/grafana/pkg/apimachinery/errutil" +) + +var ( + ErrPrometheusRuleValidationFailed = errutil.ValidationFailed("alerting.prometheusRuleInvalid") ) type PrometheusRulesFile struct { @@ -23,3 +29,11 @@ type PrometheusRule struct { Annotations map[string]string `yaml:"annotations,omitempty"` Record string `yaml:"record,omitempty"` } + +func (r *PrometheusRule) Validate() error { + if r.KeepFiringFor != nil { + return ErrPrometheusRuleValidationFailed.Errorf("keep_firing_for is not supported") + } + + return nil +} diff --git a/pkg/services/ngalert/store/alert_rule.go b/pkg/services/ngalert/store/alert_rule.go index d688d6bb53d..0d11d9eaaaa 100644 --- a/pkg/services/ngalert/store/alert_rule.go +++ b/pkg/services/ngalert/store/alert_rule.go @@ -648,36 +648,6 @@ func (st DBstore) GetRuleGroupInterval(ctx context.Context, orgID int64, namespa }) } -// GetUserVisibleNamespaces returns the folders that are visible to the user -func (st DBstore) GetUserVisibleNamespaces(ctx context.Context, orgID int64, user identity.Requester) (map[string]*folder.Folder, error) { - folders, err := st.FolderService.GetFolders(ctx, folder.GetFoldersQuery{ - OrgID: orgID, - WithFullpath: true, - SignedInUser: user, - }) - if err != nil { - return nil, err - } - - namespaceMap := make(map[string]*folder.Folder) - for _, f := range folders { - namespaceMap[f.UID] = f - } - return namespaceMap, nil -} - -// GetNamespaceByUID is a handler for retrieving a namespace by its UID. Alerting rules follow a Grafana folder-like structure which we call namespaces. -func (st DBstore) GetNamespaceByUID(ctx context.Context, uid string, orgID int64, user identity.Requester) (*folder.Folder, error) { - f, err := st.FolderService.GetFolders(ctx, folder.GetFoldersQuery{OrgID: orgID, UIDs: []string{uid}, WithFullpath: true, SignedInUser: user}) - if err != nil { - return nil, err - } - if len(f) == 0 { - return nil, dashboards.ErrFolderAccessDenied - } - return f[0], nil -} - func (st DBstore) GetAlertRulesKeysForScheduling(ctx context.Context) ([]ngmodels.AlertRuleKeyWithVersion, error) { var result []ngmodels.AlertRuleKeyWithVersion err := st.SQLStore.WithDbSession(ctx, func(sess *db.Session) error { diff --git a/pkg/services/ngalert/store/alert_rule_test.go b/pkg/services/ngalert/store/alert_rule_test.go index 79b8f704681..6f6f9453e18 100644 --- a/pkg/services/ngalert/store/alert_rule_test.go +++ b/pkg/services/ngalert/store/alert_rule_test.go @@ -782,58 +782,6 @@ func TestIntegration_DeleteAlertRulesByUID(t *testing.T) { }) } -func TestIntegration_GetNamespaceByUID(t *testing.T) { - if testing.Short() { - t.Skip("skipping integration test") - } - - sqlStore := db.InitTestDB(t) - cfg := setting.NewCfg() - folderService := setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures()) - b := &fakeBus{} - logger := log.New("test-dbstore") - store := createTestStore(sqlStore, folderService, logger, cfg.UnifiedAlerting, b) - - u := &user.SignedInUser{ - UserID: 1, - OrgID: 1, - OrgRole: org.RoleAdmin, - IsGrafanaAdmin: true, - } - - uid := uuid.NewString() - parentUid := uuid.NewString() - title := "folder/title" - parentTitle := "parent-title" - createFolder(t, store, parentUid, parentTitle, 1, "") - createFolder(t, store, uid, title, 1, parentUid) - - actual, err := store.GetNamespaceByUID(context.Background(), uid, 1, u) - require.NoError(t, err) - require.Equal(t, title, actual.Title) - require.Equal(t, uid, actual.UID) - require.Equal(t, title, actual.Fullpath) - - t.Run("error when user does not have permissions", func(t *testing.T) { - someUser := &user.SignedInUser{ - UserID: 2, - OrgID: 1, - OrgRole: org.RoleViewer, - } - _, err = store.GetNamespaceByUID(context.Background(), uid, 1, someUser) - require.ErrorIs(t, err, dashboards.ErrFolderAccessDenied) - }) - - t.Run("when nested folders are enabled full path should be populated with correct value", func(t *testing.T) { - store.FolderService = setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)) - actual, err := store.GetNamespaceByUID(context.Background(), uid, 1, u) - require.NoError(t, err) - require.Equal(t, title, actual.Title) - require.Equal(t, uid, actual.UID) - require.Equal(t, "parent-title/folder\\/title", actual.Fullpath) - }) -} - func TestIntegrationInsertAlertRules(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") diff --git a/pkg/services/ngalert/store/namespace.go b/pkg/services/ngalert/store/namespace.go new file mode 100644 index 00000000000..9279ff840f9 --- /dev/null +++ b/pkg/services/ngalert/store/namespace.go @@ -0,0 +1,97 @@ +package store + +import ( + "context" + "errors" + "sort" + + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/folder" +) + +// GetUserVisibleNamespaces returns the folders that are visible to the user +func (st DBstore) GetUserVisibleNamespaces(ctx context.Context, orgID int64, user identity.Requester) (map[string]*folder.Folder, error) { + folders, err := st.FolderService.GetFolders(ctx, folder.GetFoldersQuery{ + OrgID: orgID, + WithFullpath: true, + SignedInUser: user, + }) + if err != nil { + return nil, err + } + + namespaceMap := make(map[string]*folder.Folder) + for _, f := range folders { + namespaceMap[f.UID] = f + } + return namespaceMap, nil +} + +// GetNamespaceByUID is a handler for retrieving a namespace by its UID. Alerting rules follow a Grafana folder-like structure which we call namespaces. +func (st DBstore) GetNamespaceByUID(ctx context.Context, uid string, orgID int64, user identity.Requester) (*folder.Folder, error) { + f, err := st.FolderService.GetFolders(ctx, folder.GetFoldersQuery{OrgID: orgID, UIDs: []string{uid}, WithFullpath: true, SignedInUser: user}) + if err != nil { + return nil, err + } + if len(f) == 0 { + return nil, dashboards.ErrFolderAccessDenied + } + return f[0], nil +} + +// GetNamespaceInRootByTitle gets namespace by its title in the root folder. +func (st DBstore) GetNamespaceInRootByTitle(ctx context.Context, title string, orgID int64, user identity.Requester) (*folder.Folder, error) { + q := &folder.GetChildrenQuery{ + UID: folder.RootFolderUID, + OrgID: orgID, + SignedInUser: user, + } + folders, err := st.FolderService.GetChildren(ctx, q) + if err != nil { + return nil, err + } + + foundByTitle := []*folder.Folder{} + for _, f := range folders { + if f.Title == title && f.ParentUID == folder.RootFolderUID { + foundByTitle = append(foundByTitle, f) + } + } + + if len(foundByTitle) == 0 { + return nil, dashboards.ErrFolderAccessDenied + } + + // Sort by UID to return the first folder in case of multiple folders with the same title + sort.Slice(foundByTitle, func(i, j int) bool { + return foundByTitle[i].UID < foundByTitle[j].UID + }) + + return foundByTitle[0], nil +} + +// GetOrCreateNamespaceInRootByTitle gets or creates a namespace by title in the _root_ folder. +func (st DBstore) GetOrCreateNamespaceInRootByTitle(ctx context.Context, title string, orgID int64, user identity.Requester) (*folder.Folder, error) { + var f *folder.Folder + var err error + + f, err = st.GetNamespaceInRootByTitle(ctx, title, orgID, user) + if err != nil && !errors.Is(err, dashboards.ErrFolderAccessDenied) { + return nil, err + } + + if f == nil { + cmd := &folder.CreateFolderCommand{ + OrgID: orgID, + Title: title, + SignedInUser: user, + } + f, err = st.FolderService.Create(ctx, cmd) + if err != nil { + return nil, err + } + } + + return f, nil +} diff --git a/pkg/services/ngalert/store/namespace_test.go b/pkg/services/ngalert/store/namespace_test.go new file mode 100644 index 00000000000..8ba163a1080 --- /dev/null +++ b/pkg/services/ngalert/store/namespace_test.go @@ -0,0 +1,220 @@ +package store + +import ( + "context" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/folder" + "github.com/grafana/grafana/pkg/services/org" + "github.com/grafana/grafana/pkg/services/user" + + "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/setting" +) + +func TestIntegration_GetUserVisibleNamespaces(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + sqlStore := db.InitTestDB(t) + cfg := setting.NewCfg() + folderService := setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures()) + b := &fakeBus{} + logger := log.New("test-dbstore") + store := createTestStore(sqlStore, folderService, logger, cfg.UnifiedAlerting, b) + + admin := &user.SignedInUser{ + UserID: 1, + OrgID: 1, + OrgRole: org.RoleAdmin, + IsGrafanaAdmin: true, + } + + folders := []struct { + uid string + title string + parentUid string + }{ + {uid: uuid.NewString(), title: "folder1", parentUid: ""}, + {uid: uuid.NewString(), title: "folder2", parentUid: ""}, + {uid: uuid.NewString(), title: "nested/folder", parentUid: ""}, + } + + for _, f := range folders { + createFolder(t, store, f.uid, f.title, 1, f.parentUid) + } + + t.Run("returns all folders", func(t *testing.T) { + namespaces, err := store.GetUserVisibleNamespaces(context.Background(), 1, admin) + require.NoError(t, err) + require.Len(t, namespaces, len(folders)) + }) + + t.Run("returns empty list for a non existing org", func(t *testing.T) { + emptyOrgID := int64(999) + namespaces, err := store.GetUserVisibleNamespaces(context.Background(), emptyOrgID, admin) + require.NoError(t, err) + require.Empty(t, namespaces) + }) +} + +func TestIntegration_GetNamespaceByUID(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + sqlStore := db.InitTestDB(t) + cfg := setting.NewCfg() + folderService := setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures()) + b := &fakeBus{} + logger := log.New("test-dbstore") + store := createTestStore(sqlStore, folderService, logger, cfg.UnifiedAlerting, b) + + u := &user.SignedInUser{ + UserID: 1, + OrgID: 1, + OrgRole: org.RoleAdmin, + IsGrafanaAdmin: true, + } + + uid := uuid.NewString() + parentUid := uuid.NewString() + title := "folder/title" + parentTitle := "parent-title" + createFolder(t, store, parentUid, parentTitle, 1, "") + createFolder(t, store, uid, title, 1, parentUid) + + actual, err := store.GetNamespaceByUID(context.Background(), uid, 1, u) + require.NoError(t, err) + require.Equal(t, title, actual.Title) + require.Equal(t, uid, actual.UID) + require.Equal(t, title, actual.Fullpath) + + t.Run("error when user does not have permissions", func(t *testing.T) { + someUser := &user.SignedInUser{ + UserID: 2, + OrgID: 1, + OrgRole: org.RoleViewer, + } + _, err = store.GetNamespaceByUID(context.Background(), uid, 1, someUser) + require.ErrorIs(t, err, dashboards.ErrFolderAccessDenied) + }) + + t.Run("error when folder does not exist", func(t *testing.T) { + nonExistentUID := uuid.NewString() + _, err := store.GetNamespaceByUID(context.Background(), nonExistentUID, 1, u) + require.ErrorIs(t, err, dashboards.ErrFolderAccessDenied) + }) + + t.Run("when nested folders are enabled full path should be populated with correct value", func(t *testing.T) { + store.FolderService = setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)) + actual, err := store.GetNamespaceByUID(context.Background(), uid, 1, u) + require.NoError(t, err) + require.Equal(t, title, actual.Title) + require.Equal(t, uid, actual.UID) + require.Equal(t, "parent-title/folder\\/title", actual.Fullpath) + }) +} + +func TestIntegration_GetNamespaceInRootByTitle(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + sqlStore := db.InitTestDB(t) + cfg := setting.NewCfg() + folderService := setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures()) + b := &fakeBus{} + logger := log.New("test-dbstore") + store := createTestStore(sqlStore, folderService, logger, cfg.UnifiedAlerting, b) + store.FolderService = setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)) + + u := &user.SignedInUser{ + UserID: 1, + OrgID: 1, + OrgRole: org.RoleAdmin, + IsGrafanaAdmin: true, + } + + uid := uuid.NewString() + title := "folder-title" + createFolder(t, store, uid, title, 1, "") + + actual, err := store.GetNamespaceInRootByTitle(context.Background(), title, 1, u) + require.NoError(t, err) + require.Equal(t, title, actual.Title) + require.Equal(t, uid, actual.UID) +} + +func TestIntegration_GetOrCreateNamespaceInRootByTitle(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + u := &user.SignedInUser{ + UserID: 1, + OrgID: 1, + OrgRole: org.RoleAdmin, + IsGrafanaAdmin: true, + } + + setupStore := func(t *testing.T) *DBstore { + sqlStore := db.InitTestDB(t) + cfg := setting.NewCfg() + folderService := setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures()) + b := &fakeBus{} + logger := log.New("test-dbstore") + store := createTestStore(sqlStore, folderService, logger, cfg.UnifiedAlerting, b) + store.FolderService = setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)) + + return store + } + + t.Run("should create folder when it does not exist", func(t *testing.T) { + store := setupStore(t) + + f, err := store.GetOrCreateNamespaceInRootByTitle(context.Background(), "new folder", 1, u) + require.NoError(t, err) + require.Equal(t, "new folder", f.Title) + require.NotEmpty(t, f.UID) + + folders, err := store.FolderService.GetFolders( + context.Background(), + folder.GetFoldersQuery{ + OrgID: 1, + WithFullpath: true, + SignedInUser: u, + }, + ) + require.NoError(t, err) + require.Len(t, folders, 1) + }) + + t.Run("should return existing folder when it exists", func(t *testing.T) { + store := setupStore(t) + + title := "existing folder" + createFolder(t, store, "", title, 1, "") + f, err := store.GetOrCreateNamespaceInRootByTitle(context.Background(), title, 1, u) + require.NoError(t, err) + require.Equal(t, title, f.Title) + + folders, err := store.FolderService.GetFolders( + context.Background(), + folder.GetFoldersQuery{ + OrgID: 1, + WithFullpath: true, + SignedInUser: u, + }, + ) + require.NoError(t, err) + require.Len(t, folders, 1) + }) +} diff --git a/pkg/services/ngalert/tests/fakes/rules.go b/pkg/services/ngalert/tests/fakes/rules.go index 5cdabe62857..15ebf90f40c 100644 --- a/pkg/services/ngalert/tests/fakes/rules.go +++ b/pkg/services/ngalert/tests/fakes/rules.go @@ -258,6 +258,40 @@ func (f *RuleStore) GetNamespaceByUID(_ context.Context, uid string, orgID int64 return nil, fmt.Errorf("not found") } +func (f *RuleStore) GetOrCreateNamespaceInRootByTitle(ctx context.Context, title string, orgID int64, user identity.Requester) (*folder.Folder, error) { + f.mtx.Lock() + defer f.mtx.Unlock() + + for _, folder := range f.Folders[orgID] { + if folder.Title == title { + return folder, nil + } + } + + newFolder := &folder.Folder{ + ID: rand.Int63(), // nolint:staticcheck + UID: util.GenerateShortUID(), + Title: title, + Fullpath: "fullpath_" + title, + } + + f.Folders[orgID] = append(f.Folders[orgID], newFolder) + return newFolder, nil +} + +func (f *RuleStore) GetNamespaceInRootByTitle(ctx context.Context, title string, orgID int64, user identity.Requester) (*folder.Folder, error) { + f.mtx.Lock() + defer f.mtx.Unlock() + + for _, folder := range f.Folders[orgID] { + if folder.Title == title && folder.ParentUID == "" { + return folder, nil + } + } + + return nil, fmt.Errorf("namespace with title '%s' not found", title) +} + func (f *RuleStore) UpdateAlertRules(_ context.Context, _ *models.UserUID, q []models.UpdateRule) error { f.mtx.Lock() defer f.mtx.Unlock() diff --git a/pkg/services/notifications/webhook.go b/pkg/services/notifications/webhook.go index be92ff76b40..c912645bd55 100644 --- a/pkg/services/notifications/webhook.go +++ b/pkg/services/notifications/webhook.go @@ -10,7 +10,7 @@ import ( "net/http" "net/url" - alertingReceivers "github.com/grafana/alerting/receivers" + alertingHTTP "github.com/grafana/alerting/http" "github.com/grafana/grafana/pkg/util" ) @@ -71,7 +71,7 @@ func (ns *NotificationService) sendWebRequestSync(ctx context.Context, webhook * request.Header.Set(k, v) } - resp, err := alertingReceivers.NewTLSClient(webhook.TLSConfig).Do(request) + resp, err := alertingHTTP.NewTLSClient(webhook.TLSConfig).Do(request) if err != nil { return redactURL(err) } diff --git a/pkg/storage/unified/apistore/go.mod b/pkg/storage/unified/apistore/go.mod index b7abdb3bb91..16a36800ba0 100644 --- a/pkg/storage/unified/apistore/go.mod +++ b/pkg/storage/unified/apistore/go.mod @@ -192,7 +192,7 @@ require ( github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/grafana/alerting v0.0.0-20250221202230-9d7e00921e44 // indirect + github.com/grafana/alerting v0.0.0-20250224133628-2acbeef29642 // indirect github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 // indirect @@ -366,14 +366,14 @@ require ( go.uber.org/mock v0.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.32.0 // indirect + golang.org/x/crypto v0.35.0 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/oauth2 v0.26.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect - golang.org/x/term v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/term v0.29.0 // indirect + golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.9.0 // indirect golang.org/x/tools v0.29.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect diff --git a/pkg/storage/unified/apistore/go.sum b/pkg/storage/unified/apistore/go.sum index 87e7727af96..27c6a2641e9 100644 --- a/pkg/storage/unified/apistore/go.sum +++ b/pkg/storage/unified/apistore/go.sum @@ -566,8 +566,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/alerting v0.0.0-20250221202230-9d7e00921e44 h1:vboqvbAO0s0CTALHnqfmNvhCP1ziBcZNpYDbORqvOgg= -github.com/grafana/alerting v0.0.0-20250221202230-9d7e00921e44/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= +github.com/grafana/alerting v0.0.0-20250224133628-2acbeef29642 h1:iQ0h/h+QoguSZDF+ZpPxcM/C+m1kjh+aXjMpxywowPA= +github.com/grafana/alerting v0.0.0-20250224133628-2acbeef29642/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 h1:NTMmow+74I3Jb033xhbRgWQS7A//5TDhiM4tl7bsVP4= github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7/go.mod h1:T3X4z0ejGfJOiOmZLFeKCRT/yxWJq/RtclAc/PHj/w4= github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 h1:EokLC5grHwLPs4tXW8T6E8187H1e5G9AP0QQ5B60HbA= @@ -1174,8 +1174,8 @@ golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1263,8 +1263,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1273,8 +1273,8 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= -golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1370,8 +1370,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1385,8 +1385,8 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/pkg/storage/unified/sql/continue.go b/pkg/storage/unified/resource/continue.go similarity index 97% rename from pkg/storage/unified/sql/continue.go rename to pkg/storage/unified/resource/continue.go index 77bcac8c5d4..70ca18a89ba 100644 --- a/pkg/storage/unified/sql/continue.go +++ b/pkg/storage/unified/resource/continue.go @@ -1,4 +1,4 @@ -package sql +package resource import ( "encoding/base64" diff --git a/pkg/storage/unified/resource/continue_test.go b/pkg/storage/unified/resource/continue_test.go new file mode 100644 index 00000000000..d82b74a4d85 --- /dev/null +++ b/pkg/storage/unified/resource/continue_test.go @@ -0,0 +1,15 @@ +package resource + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestContinueToken(t *testing.T) { + token := &ContinueToken{ + ResourceVersion: 100, + StartOffset: 50, + } + assert.Equal(t, "eyJvIjo1MCwidiI6MTAwfQ==", token.String()) +} diff --git a/pkg/storage/unified/resource/go.mod b/pkg/storage/unified/resource/go.mod index 833dd96a890..76a89ac0d4f 100644 --- a/pkg/storage/unified/resource/go.mod +++ b/pkg/storage/unified/resource/go.mod @@ -117,7 +117,7 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grafana/alerting v0.0.0-20250221202230-9d7e00921e44 // indirect + github.com/grafana/alerting v0.0.0-20250224133628-2acbeef29642 // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect github.com/grafana/grafana-app-sdk/logging v0.30.0 // indirect github.com/grafana/grafana-aws-sdk v0.31.5 // indirect @@ -217,14 +217,14 @@ require ( go.opentelemetry.io/otel/sdk v1.34.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/atomic v1.11.0 // indirect - golang.org/x/crypto v0.32.0 // indirect + golang.org/x/crypto v0.35.0 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/oauth2 v0.26.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sys v0.30.0 // indirect - golang.org/x/term v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/term v0.29.0 // indirect + golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.9.0 // indirect golang.org/x/tools v0.29.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect diff --git a/pkg/storage/unified/resource/go.sum b/pkg/storage/unified/resource/go.sum index 7d566e2e658..a912c869d28 100644 --- a/pkg/storage/unified/resource/go.sum +++ b/pkg/storage/unified/resource/go.sum @@ -397,8 +397,8 @@ github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grafana/alerting v0.0.0-20250221202230-9d7e00921e44 h1:vboqvbAO0s0CTALHnqfmNvhCP1ziBcZNpYDbORqvOgg= -github.com/grafana/alerting v0.0.0-20250221202230-9d7e00921e44/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= +github.com/grafana/alerting v0.0.0-20250224133628-2acbeef29642 h1:iQ0h/h+QoguSZDF+ZpPxcM/C+m1kjh+aXjMpxywowPA= +github.com/grafana/alerting v0.0.0-20250224133628-2acbeef29642/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 h1:NTMmow+74I3Jb033xhbRgWQS7A//5TDhiM4tl7bsVP4= github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7/go.mod h1:T3X4z0ejGfJOiOmZLFeKCRT/yxWJq/RtclAc/PHj/w4= github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 h1:EokLC5grHwLPs4tXW8T6E8187H1e5G9AP0QQ5B60HbA= @@ -824,8 +824,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= @@ -866,14 +866,14 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= -golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -931,8 +931,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -942,8 +942,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= diff --git a/pkg/storage/unified/resource/server.go b/pkg/storage/unified/resource/server.go index 4ef0c456cac..7853f9125db 100644 --- a/pkg/storage/unified/resource/server.go +++ b/pkg/storage/unified/resource/server.go @@ -923,6 +923,10 @@ func (s *server) initWatcher() error { // pipe all events v := <-events + if v == nil { + s.log.Error("received nil event") + continue + } // Skip events during batch updates if v.PreviousRV < 0 { continue diff --git a/pkg/storage/unified/sql/backend.go b/pkg/storage/unified/sql/backend.go index 4c6d00addd4..024bc5ab7d0 100644 --- a/pkg/storage/unified/sql/backend.go +++ b/pkg/storage/unified/sql/backend.go @@ -588,11 +588,11 @@ type listIter struct { // ContinueToken implements resource.ListIterator. func (l *listIter) ContinueToken() string { - return ContinueToken{ResourceVersion: l.listRV, StartOffset: l.offset}.String() + return resource.ContinueToken{ResourceVersion: l.listRV, StartOffset: l.offset}.String() } func (l *listIter) ContinueTokenWithCurrentRV() string { - return ContinueToken{ResourceVersion: l.rv, StartOffset: l.offset}.String() + return resource.ContinueToken{ResourceVersion: l.rv, StartOffset: l.offset}.String() } func (l *listIter) Error() error { @@ -679,7 +679,7 @@ func (b *backend) listAtRevision(ctx context.Context, req *resource.ListRequest, // Get the RV iter := &listIter{listRV: req.ResourceVersion} if req.NextPageToken != "" { - continueToken, err := GetContinueToken(req.NextPageToken) + continueToken, err := resource.GetContinueToken(req.NextPageToken) if err != nil { return 0, fmt.Errorf("get continue token: %w", err) } @@ -737,7 +737,7 @@ func (b *backend) getHistory(ctx context.Context, req *resource.ListRequest, cb iter := &listIter{} if req.NextPageToken != "" { - continueToken, err := GetContinueToken(req.NextPageToken) + continueToken, err := resource.GetContinueToken(req.NextPageToken) if err != nil { return 0, fmt.Errorf("get continue token: %w", err) } diff --git a/pkg/storage/unified/sql/server.go b/pkg/storage/unified/sql/server.go index a9778d0c42d..0bd5e6976a6 100644 --- a/pkg/storage/unified/sql/server.go +++ b/pkg/storage/unified/sql/server.go @@ -45,14 +45,7 @@ func NewResourceServer(db infraDB.DB, cfg *setting.Cfg, return nil, err } - dbCfg := cfg.SectionWithEnvOverrides("database") - // Check in the config if HA is enabled by default we always assume a HA setup. - isHA := dbCfg.Key("high_availability").MustBool(true) - // SQLite is not possible to run in HA, so we set it to false. - databaseType := dbCfg.Key("type").MustString(migrator.SQLite) - if databaseType == migrator.SQLite { - isHA = false - } + isHA := isHighAvailabilityEnabled(cfg.SectionWithEnvOverrides("database")) store, err := NewBackend(BackendOptions{DBProvider: eDB, Tracer: tracer, IsHA: isHA}) if err != nil { @@ -70,3 +63,19 @@ func NewResourceServer(db infraDB.DB, cfg *setting.Cfg, return rs, nil } + +// isHighAvailabilityEnabled determines if high availability mode should +// be enabled based on database configuration. High availability is enabled +// by default except for SQLite databases. +func isHighAvailabilityEnabled(dbCfg *setting.DynamicSection) bool { + // Check in the config if HA is enabled - by default we always assume a HA setup. + isHA := dbCfg.Key("high_availability").MustBool(true) + + // SQLite is not possible to run in HA, so we force it to false. + databaseType := dbCfg.Key("type").String() + if databaseType == migrator.SQLite { + isHA = false + } + + return isHA +} diff --git a/pkg/storage/unified/sql/server_test.go b/pkg/storage/unified/sql/server_test.go new file mode 100644 index 00000000000..992319c12b2 --- /dev/null +++ b/pkg/storage/unified/sql/server_test.go @@ -0,0 +1,100 @@ +package sql + +import ( + "strconv" + "testing" + + "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + "github.com/grafana/grafana/pkg/setting" + "github.com/stretchr/testify/require" +) + +func TestIsHighAvailabilityEnabled(t *testing.T) { + tests := []struct { + name string + dbType string + haConfigValue *bool + isHA bool + }{ + { + name: "SQLite should never have HA enabled", + dbType: migrator.SQLite, + haConfigValue: boolPtr(true), + isHA: false, + }, + { + name: "MySQL with HA enabled in config should default to true", + dbType: migrator.MySQL, + haConfigValue: boolPtr(true), + isHA: true, + }, + { + name: "MySQL with HA disabled in config should default to false", + dbType: migrator.MySQL, + haConfigValue: boolPtr(false), + isHA: false, + }, + { + name: "MySQL with no HA config should default to true", + dbType: migrator.MySQL, + haConfigValue: nil, + isHA: true, + }, + { + name: "Postgres with HA enabled in config should default to true", + dbType: migrator.Postgres, + haConfigValue: boolPtr(true), + isHA: true, + }, + { + name: "Postgres with HA disabled in config should default to false", + dbType: migrator.Postgres, + haConfigValue: boolPtr(false), + isHA: false, + }, + { + name: "Postgres with no HA config should default to true", + dbType: migrator.Postgres, + haConfigValue: nil, + isHA: true, + }, + { + name: "No database type set should default to true", + dbType: "", + haConfigValue: nil, + isHA: true, + }, + { + name: "No database type set with HA enabled in config should default to true", + dbType: "", + haConfigValue: boolPtr(true), + isHA: true, + }, + { + name: "No database type set with HA disabled in config should default to false", + dbType: "", + haConfigValue: boolPtr(false), + isHA: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := setting.NewCfg().SectionWithEnvOverrides("database") + if tt.dbType != "" { + cfg.Key("type").SetValue(tt.dbType) + } + + if tt.haConfigValue != nil { + cfg.Key("high_availability").SetValue(strconv.FormatBool(*tt.haConfigValue)) + } + + result := isHighAvailabilityEnabled(cfg) + require.Equal(t, tt.isHA, result) + }) + } +} + +func boolPtr(b bool) *bool { + return &b +} diff --git a/pkg/storage/unified/testing/storage_backend.go b/pkg/storage/unified/testing/storage_backend.go index d700f6cbbd7..d34cf3c0c71 100644 --- a/pkg/storage/unified/testing/storage_backend.go +++ b/pkg/storage/unified/testing/storage_backend.go @@ -15,7 +15,6 @@ import ( "github.com/grafana/authlib/types" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/storage/unified/resource" - "github.com/grafana/grafana/pkg/storage/unified/sql" "github.com/grafana/grafana/pkg/util/testutil" ) @@ -34,6 +33,7 @@ type NewBackendFunc func(ctx context.Context) resource.StorageBackend // TestOptions configures which tests to run type TestOptions struct { SkipTests map[string]bool // tests to skip + } // RunStorageBackendTest runs the storage backend test suite @@ -294,21 +294,29 @@ func runTestIntegrationBackendList(t *testing.T, backend resource.StorageBackend server := newServer(t, backend) // Create a few resources before starting the watch - rv1, _ := writeEvent(ctx, backend, "item1", resource.WatchEvent_ADDED) + rv1, err := writeEvent(ctx, backend, "item1", resource.WatchEvent_ADDED) + require.NoError(t, err) require.Greater(t, rv1, int64(0)) - rv2, _ := writeEvent(ctx, backend, "item2", resource.WatchEvent_ADDED) + rv2, err := writeEvent(ctx, backend, "item2", resource.WatchEvent_ADDED) + require.NoError(t, err) require.Greater(t, rv2, rv1) - rv3, _ := writeEvent(ctx, backend, "item3", resource.WatchEvent_ADDED) + rv3, err := writeEvent(ctx, backend, "item3", resource.WatchEvent_ADDED) + require.NoError(t, err) require.Greater(t, rv3, rv2) - rv4, _ := writeEvent(ctx, backend, "item4", resource.WatchEvent_ADDED) + rv4, err := writeEvent(ctx, backend, "item4", resource.WatchEvent_ADDED) + require.NoError(t, err) require.Greater(t, rv4, rv3) - rv5, _ := writeEvent(ctx, backend, "item5", resource.WatchEvent_ADDED) + rv5, err := writeEvent(ctx, backend, "item5", resource.WatchEvent_ADDED) + require.NoError(t, err) require.Greater(t, rv5, rv4) - rv6, _ := writeEvent(ctx, backend, "item2", resource.WatchEvent_MODIFIED) + rv6, err := writeEvent(ctx, backend, "item2", resource.WatchEvent_MODIFIED) + require.NoError(t, err) require.Greater(t, rv6, rv5) - rv7, _ := writeEvent(ctx, backend, "item3", resource.WatchEvent_DELETED) + rv7, err := writeEvent(ctx, backend, "item3", resource.WatchEvent_DELETED) + require.NoError(t, err) require.Greater(t, rv7, rv6) - rv8, _ := writeEvent(ctx, backend, "item6", resource.WatchEvent_ADDED) + rv8, err := writeEvent(ctx, backend, "item6", resource.WatchEvent_ADDED) + require.NoError(t, err) require.Greater(t, rv8, rv7) t.Run("fetch all latest", func(t *testing.T) { @@ -346,12 +354,12 @@ func runTestIntegrationBackendList(t *testing.T, backend resource.StorageBackend require.NoError(t, err) require.Nil(t, res.Error) require.Len(t, res.Items, 3) - continueToken, err := sql.GetContinueToken(res.NextPageToken) + continueToken, err := resource.GetContinueToken(res.NextPageToken) require.NoError(t, err) require.Equal(t, "item1 ADDED", string(res.Items[0].Value)) require.Equal(t, "item2 MODIFIED", string(res.Items[1].Value)) require.Equal(t, "item4 ADDED", string(res.Items[2].Value)) - require.Equal(t, rv8, continueToken.ResourceVersion) + require.GreaterOrEqual(t, continueToken.ResourceVersion, rv8) }) t.Run("list at revision", func(t *testing.T) { @@ -394,13 +402,13 @@ func runTestIntegrationBackendList(t *testing.T, backend resource.StorageBackend require.Equal(t, "item2 MODIFIED", string(res.Items[1].Value)) require.Equal(t, "item4 ADDED", string(res.Items[2].Value)) - continueToken, err := sql.GetContinueToken(res.NextPageToken) + continueToken, err := resource.GetContinueToken(res.NextPageToken) require.NoError(t, err) require.Equal(t, rv7, continueToken.ResourceVersion) }) t.Run("fetch second page at revision", func(t *testing.T) { - continueToken := &sql.ContinueToken{ + continueToken := &resource.ContinueToken{ ResourceVersion: rv8, StartOffset: 2, } @@ -421,7 +429,7 @@ func runTestIntegrationBackendList(t *testing.T, backend resource.StorageBackend require.Equal(t, "item4 ADDED", string(res.Items[0].Value)) require.Equal(t, "item5 ADDED", string(res.Items[1].Value)) - continueToken, err = sql.GetContinueToken(res.NextPageToken) + continueToken, err = resource.GetContinueToken(res.NextPageToken) require.NoError(t, err) require.Equal(t, rv8, continueToken.ResourceVersion) require.Equal(t, int64(4), continueToken.StartOffset) @@ -478,14 +486,14 @@ func runTestIntegrationBackendListHistory(t *testing.T, backend resource.Storage require.Equal(t, "item1 MODIFIED", string(res.Items[2].Value)) require.Equal(t, rvHistory3, res.Items[2].ResourceVersion) - continueToken, err := sql.GetContinueToken(res.NextPageToken) + continueToken, err := resource.GetContinueToken(res.NextPageToken) require.NoError(t, err) // should return the furthest back RV as the next page token require.Equal(t, rvHistory3, continueToken.ResourceVersion) }) t.Run("fetch second page of history at revision", func(t *testing.T) { - continueToken := &sql.ContinueToken{ + continueToken := &resource.ContinueToken{ ResourceVersion: rvHistory3, StartOffset: 2, } diff --git a/pkg/tests/api/alerting/api_convert_prometheus_test.go b/pkg/tests/api/alerting/api_convert_prometheus_test.go new file mode 100644 index 00000000000..326063a15ce --- /dev/null +++ b/pkg/tests/api/alerting/api_convert_prometheus_test.go @@ -0,0 +1,196 @@ +package alerting + +import ( + "testing" + "time" + + prommodel "github.com/prometheus/common/model" + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/services/datasources" + apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" + "github.com/grafana/grafana/pkg/services/org" + "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/tests/testinfra" + "github.com/grafana/grafana/pkg/util" +) + +func TestIntegrationConvertPrometheusEndpoints(t *testing.T) { + testinfra.SQLiteIntegrationTest(t) + + // Setup Grafana and its Database + dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ + DisableLegacyAlerting: true, + EnableUnifiedAlerting: true, + DisableAnonymous: true, + AppModeProduction: true, + EnableFeatureToggles: []string{"alertingConversionAPI"}, + }) + + grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path) + + // Create a user to make authenticated requests + createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ + DefaultOrgRole: string(org.RoleAdmin), + Password: "password", + Login: "admin", + }) + + apiClient := newAlertingApiClient(grafanaListedAddr, "admin", "password") + namespace := "test-namespace" + + promGroup1 := apimodels.PrometheusRuleGroup{ + Name: "test-group-1", + Interval: prommodel.Duration(60 * time.Second), + Rules: []apimodels.PrometheusRule{ + // Recording rule + { + Record: "test:requests:rate5m", + Expr: "sum(rate(test_requests_total[5m])) by (job)", + Labels: map[string]string{ + "env": "prod", + "team": "infra", + }, + }, + // Two alerting rules + { + Alert: "HighMemoryUsage", + Expr: "process_memory_usage > 80", + For: util.Pointer(prommodel.Duration(5 * time.Minute)), + Labels: map[string]string{ + "severity": "warning", + "team": "alerting", + }, + Annotations: map[string]string{ + "annotation-1": "value-1", + "annotation-2": "value-2", + }, + }, + { + Alert: "ServiceDown", + Expr: "up == 0", + For: util.Pointer(prommodel.Duration(2 * time.Minute)), + Labels: map[string]string{ + "severity": "critical", + }, + Annotations: map[string]string{ + "annotation-1": "value-1", + }, + }, + }, + } + + promGroup2 := apimodels.PrometheusRuleGroup{ + Name: "test-group-2", + Interval: prommodel.Duration(60 * time.Second), + Rules: []apimodels.PrometheusRule{ + { + Alert: "HighDiskUsage", + Expr: "disk_usage > 80", + For: util.Pointer(prommodel.Duration(1 * time.Minute)), + Labels: map[string]string{ + "severity": "low", + "team": "alerting", + }, + Annotations: map[string]string{ + "annotation-5": "value-5", + }, + }, + }, + } + + ds := apiClient.CreateDatasource(t, datasources.DS_PROMETHEUS) + + t.Run("create two rule groups and get them back", func(t *testing.T) { + apiClient.ConvertPrometheusPostRuleGroup(t, namespace, ds.Body.Datasource.UID, promGroup1, nil) + apiClient.ConvertPrometheusPostRuleGroup(t, namespace, ds.Body.Datasource.UID, promGroup2, nil) + + ns, _, _ := apiClient.GetAllRulesWithStatus(t) + + require.Len(t, ns[namespace], 2) + + rulesByGroupName := map[string][]apimodels.GettableExtendedRuleNode{} + for _, group := range ns[namespace] { + rulesByGroupName[group.Name] = append(rulesByGroupName[group.Name], group.Rules...) + } + + require.Len(t, rulesByGroupName[promGroup1.Name], 3) + require.Len(t, rulesByGroupName[promGroup2.Name], 1) + }) + + t.Run("when pausing header is set, rules should be paused", func(t *testing.T) { + tests := []struct { + name string + recordingPaused bool + alertPaused bool + }{ + { + name: "do not pause rules", + recordingPaused: false, + alertPaused: false, + }, + { + name: "pause recording rules", + recordingPaused: true, + alertPaused: false, + }, + { + name: "pause alert rules", + recordingPaused: false, + alertPaused: true, + }, + { + name: "pause both recording and alert rules", + recordingPaused: true, + alertPaused: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + headers := map[string]string{} + if tc.recordingPaused { + headers["X-Grafana-Alerting-Recording-Rules-Paused"] = "true" + } + if tc.alertPaused { + headers["X-Grafana-Alerting-Alert-Rules-Paused"] = "true" + } + apiClient.ConvertPrometheusPostRuleGroup(t, namespace, ds.Body.Datasource.UID, promGroup1, headers) + + ns, _, _ := apiClient.GetAllRulesWithStatus(t) + + rulesByGroupName := map[string][]apimodels.GettableExtendedRuleNode{} + for _, group := range ns[namespace] { + rulesByGroupName[group.Name] = append(rulesByGroupName[group.Name], group.Rules...) + } + + require.Len(t, rulesByGroupName[promGroup1.Name], 3) + + pausedRecordingRules := 0 + pausedAlertRules := 0 + + for _, rule := range rulesByGroupName[promGroup1.Name] { + if rule.GrafanaManagedAlert.IsPaused { + if rule.GrafanaManagedAlert.Record != nil { + pausedRecordingRules++ + } else { + pausedAlertRules++ + } + } + } + + if tc.recordingPaused { + require.Equal(t, 1, pausedRecordingRules) + } else { + require.Equal(t, 0, pausedRecordingRules) + } + + if tc.alertPaused { + require.Equal(t, 2, pausedAlertRules) + } else { + require.Equal(t, 0, pausedAlertRules) + } + }) + } + }) +} diff --git a/pkg/tests/api/alerting/testing.go b/pkg/tests/api/alerting/testing.go index 5272883af9e..de927dc890c 100644 --- a/pkg/tests/api/alerting/testing.go +++ b/pkg/tests/api/alerting/testing.go @@ -18,6 +18,7 @@ import ( "github.com/prometheus/common/model" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" "github.com/grafana/grafana/pkg/api" "github.com/grafana/grafana/pkg/expr" @@ -752,7 +753,13 @@ func (a apiClient) SubmitRuleForTesting(t *testing.T, config apimodels.PostableE func (a apiClient) CreateTestDatasource(t *testing.T) (result api.CreateOrUpdateDatasourceResponse) { t.Helper() - payload := fmt.Sprintf(`{"name":"TestData-%s","type":"testdata","access":"proxy","isDefault":false}`, uuid.NewString()) + return a.CreateDatasource(t, "testdata") +} + +func (a apiClient) CreateDatasource(t *testing.T, dsType string) (result api.CreateOrUpdateDatasourceResponse) { + t.Helper() + + payload := fmt.Sprintf(`{"name":"TestDatasource-%s","type":"%s","access":"proxy","isDefault":false}`, uuid.NewString(), dsType) buf := bytes.Buffer{} buf.Write([]byte(payload)) @@ -1094,6 +1101,25 @@ func (a apiClient) GetRuleByUID(t *testing.T, ruleUID string) apimodels.Gettable return rule } +func (a apiClient) ConvertPrometheusPostRuleGroup(t *testing.T, namespaceTitle, datasourceUID string, promGroup apimodels.PrometheusRuleGroup, headers map[string]string) { + t.Helper() + + data, err := yaml.Marshal(promGroup) + require.NoError(t, err) + buf := bytes.NewReader(data) + + req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/convert/prometheus/config/v1/rules/%s", a.url, namespaceTitle), buf) + require.NoError(t, err) + req.Header.Add("X-Grafana-Alerting-Datasource-UID", datasourceUID) + + for key, value := range headers { + req.Header.Add(key, value) + } + + _, status, raw := sendRequest[apimodels.ConvertPrometheusResponse](t, req, http.StatusAccepted) + requireStatusCode(t, http.StatusAccepted, status, raw) +} + func sendRequest[T any](t *testing.T, req *http.Request, successStatusCode int) (T, int, string) { t.Helper() client := &http.Client{} diff --git a/public/api-merged.json b/public/api-merged.json index 09bd1962ae9..dc455b6a36f 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -22771,7 +22771,6 @@ } }, "gettableAlerts": { - "description": "GettableAlerts gettable alerts", "type": "array", "items": { "type": "object", @@ -22896,7 +22895,6 @@ } }, "gettableSilences": { - "description": "GettableSilences gettable silences", "type": "array", "items": { "type": "object", diff --git a/public/app/core/components/AppChrome/MegaMenu/MegaMenuItem.tsx b/public/app/core/components/AppChrome/MegaMenu/MegaMenuItem.tsx index 4379283f3c5..f643f7199d5 100644 --- a/public/app/core/components/AppChrome/MegaMenu/MegaMenuItem.tsx +++ b/public/app/core/components/AppChrome/MegaMenu/MegaMenuItem.tsx @@ -4,10 +4,9 @@ import * as React from 'react'; import { useLocation } from 'react-router-dom-v5-compat'; import { useLocalStorage } from 'react-use'; -import { GrafanaTheme2, NavModelItem, toIconName } from '@grafana/data'; -import { useStyles2, Text, IconButton, Icon, Stack, Badge } from '@grafana/ui'; +import { FeatureState, GrafanaTheme2, NavModelItem, toIconName } from '@grafana/data'; +import { useStyles2, Text, IconButton, Icon, Stack, FeatureBadge } from '@grafana/ui'; import { useGrafana } from 'app/core/context/GrafanaContext'; -import { t } from 'app/core/internationalization'; import { Indent } from '../../Indent/Indent'; @@ -36,7 +35,7 @@ export function MegaMenuItem({ link, activeItem, level = 0, onClick, onPin, isPi const isActive = link === activeItem || (level === MAX_DEPTH && hasActiveChild); const [sectionExpanded, setSectionExpanded] = useLocalStorage( `grafana.navigation.expanded[${link.text}]`, - Boolean(hasActiveChild || link.isNew) + Boolean(hasActiveChild) ); const showExpandButton = level < MAX_DEPTH && Boolean(linkHasChildren(link) || link.emptyMessage); const item = useRef(null); @@ -108,7 +107,7 @@ export function MegaMenuItem({ link, activeItem, level = 0, onClick, onPin, isPi > {level === 0 && iconElement && {iconElement}} {link.text} - {link.isNew && } + {link.isNew && } diff --git a/public/app/core/components/AppChrome/MegaMenu/utils.ts b/public/app/core/components/AppChrome/MegaMenu/utils.ts index abbcbc043c1..2afb61f8cc4 100644 --- a/public/app/core/components/AppChrome/MegaMenu/utils.ts +++ b/public/app/core/components/AppChrome/MegaMenu/utils.ts @@ -35,21 +35,36 @@ export const enrichHelpItem = (helpItem: NavModelItem) => { return helpItem; }; -export const enrichWithInteractionTracking = (item: NavModelItem, megaMenuDockedState: boolean) => { +export const enrichWithInteractionTracking = ( + item: NavModelItem, + megaMenuDockedState: boolean, + ancestorIsNew = false +) => { // creating a new object here to not mutate the original item object const newItem = { ...item }; const onClick = newItem.onClick; + + let isNew: 'item' | 'ancestor' | undefined = undefined; + if (newItem.isNew) { + isNew = 'item'; + } else if (ancestorIsNew) { + isNew = 'ancestor'; + } + newItem.onClick = () => { reportInteraction('grafana_navigation_item_clicked', { path: newItem.url ?? newItem.id, menuIsDocked: megaMenuDockedState, itemIsBookmarked: Boolean(config.featureToggles.pinNavItems && newItem?.parentItem?.id === 'bookmarks'), bookmarkToggleOn: Boolean(config.featureToggles.pinNavItems), + isNew, }); onClick?.(); }; if (newItem.children) { - newItem.children = newItem.children.map((item) => enrichWithInteractionTracking(item, megaMenuDockedState)); + newItem.children = newItem.children.map((item) => + enrichWithInteractionTracking(item, megaMenuDockedState, isNew !== undefined) + ); } return newItem; }; diff --git a/public/app/features/alerting/unified/mockGrafanaNotifiers.ts b/public/app/features/alerting/unified/mockGrafanaNotifiers.ts index a15904148ea..984a9eb1a14 100644 --- a/public/app/features/alerting/unified/mockGrafanaNotifiers.ts +++ b/public/app/features/alerting/unified/mockGrafanaNotifiers.ts @@ -3146,7 +3146,8 @@ export const grafanaAlertNotifiers: Record = { element: 'input', inputType: 'text', label: 'Subject', - description: 'Optional subject. You can use templates to customize this field', + description: + 'Optional subject. By default, this field uses the default title template and can be customized using templates. It cannot be an empty string', placeholder: '{{ template "default.title" . }}', propertyName: 'subject', selectOptions: null, @@ -3165,7 +3166,7 @@ export const grafanaAlertNotifiers: Record = { inputType: '', label: 'Message', description: - 'Optional message. You can use templates to customize this field. Using a custom message will replace the default message', + 'Optional message. By default, this field uses the default message template and can be customized with templates and custom messages', placeholder: '{{ template "default.message" . }}', propertyName: 'message', selectOptions: null, diff --git a/public/app/features/dashboard-scene/utils/utils.ts b/public/app/features/dashboard-scene/utils/utils.ts index 60768226dfc..05358cfd0d2 100644 --- a/public/app/features/dashboard-scene/utils/utils.ts +++ b/public/app/features/dashboard-scene/utils/utils.ts @@ -263,7 +263,7 @@ export function getCurrentValueForOldIntervalModel(variable: IntervalVariableMod const selectedInterval = Array.isArray(variable.current.value) ? variable.current.value[0] : variable.current.value; // If the interval is the old auto format, return the new auto interval from scenes. - if (selectedInterval.startsWith('$__auto_interval_')) { + if (selectedInterval.startsWith('$__auto_interval_') || selectedInterval === '$__auto') { return '$__auto'; } diff --git a/public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.test.ts b/public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.test.ts index 01f12b81f1c..6a64a1d02ea 100644 --- a/public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.test.ts +++ b/public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.test.ts @@ -1,162 +1,401 @@ -import { EventBusSrv, FieldType, getDefaultTimeRange, LoadingState, toDataFrame } from '@grafana/data'; +import { + EventBusSrv, + FieldConfigOptionsRegistry, + FieldConfigPropertyItem, + FieldType, + getDefaultTimeRange, + LoadingState, + PanelPlugin, + Registry, + toDataFrame, +} from '@grafana/data'; +import { VizPanel } from '@grafana/scenes'; -import { getStandardEditorContext } from './getVisualizationOptions'; +import { getStandardEditorContext, getVisualizationOptions2 } from './getVisualizationOptions'; -describe('getStandardEditorContext', () => { - it('defaults the series data to an empty array', () => { - const editorContext = getStandardEditorContext({ - data: undefined, - replaceVariables: jest.fn(), - options: {}, - eventBus: new EventBusSrv(), - instanceState: {}, +describe('getVisualizationOptions', () => { + describe('getStandardEditorContext', () => { + it('defaults the series data to an empty array', () => { + const editorContext = getStandardEditorContext({ + data: undefined, + replaceVariables: jest.fn(), + options: {}, + eventBus: new EventBusSrv(), + instanceState: {}, + }); + + expect(editorContext.data).toEqual([]); }); - expect(editorContext.data).toEqual([]); - }); + it('returns suggestions for empty data', () => { + const editorContext = getStandardEditorContext({ + data: undefined, + replaceVariables: jest.fn(), + options: {}, + eventBus: new EventBusSrv(), + instanceState: {}, + }); - it('returns suggestions for empty data', () => { - const editorContext = getStandardEditorContext({ - data: undefined, - replaceVariables: jest.fn(), - options: {}, - eventBus: new EventBusSrv(), - instanceState: {}, + expect(editorContext.getSuggestions).toBeDefined(); + expect(editorContext.getSuggestions?.()).toEqual([ + { + documentation: 'Name of the series', + label: 'Name', + origin: 'series', + value: '__series.name', + }, + { + documentation: 'Field name of the clicked datapoint (in ms epoch)', + label: 'Name', + origin: 'field', + value: '__field.name', + }, + { + documentation: 'Adds current variables', + label: 'All variables', + origin: 'template', + value: '__all_variables', + }, + { + documentation: 'Adds current time range', + label: 'Time range', + origin: 'built-in', + value: '__url_time_range', + }, + { + documentation: "Adds current time range's from value", + label: 'Time range: from', + origin: 'built-in', + value: '__from', + }, + { + documentation: "Adds current time range's to value", + label: 'Time range: to', + origin: 'built-in', + value: '__to', + }, + ]); }); - expect(editorContext.getSuggestions).toBeDefined(); - expect(editorContext.getSuggestions?.()).toEqual([ - { - documentation: 'Name of the series', - label: 'Name', - origin: 'series', - value: '__series.name', - }, - { - documentation: 'Field name of the clicked datapoint (in ms epoch)', - label: 'Name', - origin: 'field', - value: '__field.name', - }, - { - documentation: 'Adds current variables', - label: 'All variables', - origin: 'template', - value: '__all_variables', - }, - { - documentation: 'Adds current time range', - label: 'Time range', - origin: 'built-in', - value: '__url_time_range', - }, - { - documentation: "Adds current time range's from value", - label: 'Time range: from', - origin: 'built-in', - value: '__from', - }, - { - documentation: "Adds current time range's to value", - label: 'Time range: to', - origin: 'built-in', - value: '__to', - }, - ]); + it('returns suggestions for non-empty data', () => { + const series = [ + toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time }, + { name: 'score', type: FieldType.number }, + ], + }), + ]; + + const panelData = { + series, + timeRange: getDefaultTimeRange(), + state: LoadingState.Done, + }; + + const editorContext = getStandardEditorContext({ + data: panelData, + replaceVariables: jest.fn(), + options: {}, + eventBus: new EventBusSrv(), + instanceState: {}, + }); + + expect(editorContext.getSuggestions).toBeDefined(); + expect(editorContext.getSuggestions?.()).toEqual([ + { + documentation: 'Name of the series', + label: 'Name', + origin: 'series', + value: '__series.name', + }, + { + documentation: 'Field name of the clicked datapoint (in ms epoch)', + label: 'Name', + origin: 'field', + value: '__field.name', + }, + { + documentation: 'Formatted value for time on the same row', + label: 'time', + origin: 'fields', + value: '__data.fields.time', + }, + { + documentation: 'Formatted value for score on the same row', + label: 'score', + origin: 'fields', + value: '__data.fields.score', + }, + { + documentation: 'Enter the field order', + label: 'Select by index', + origin: 'fields', + value: '__data.fields[0]', + }, + { + documentation: 'the numeric field value', + label: 'Show numeric value', + origin: 'fields', + value: '__data.fields.score.numeric', + }, + { + documentation: 'the text value', + label: 'Show text value', + origin: 'fields', + value: '__data.fields.score.text', + }, + { + documentation: 'Adds current variables', + label: 'All variables', + origin: 'template', + value: '__all_variables', + }, + { + documentation: 'Adds current time range', + label: 'Time range', + origin: 'built-in', + value: '__url_time_range', + }, + { + documentation: "Adds current time range's from value", + label: 'Time range: from', + origin: 'built-in', + value: '__from', + }, + { + documentation: "Adds current time range's to value", + label: 'Time range: to', + origin: 'built-in', + value: '__to', + }, + ]); + }); }); - it('returns suggestions for non-empty data', () => { - const series = [ - toDataFrame({ - fields: [ - { name: 'time', type: FieldType.time }, - { name: 'score', type: FieldType.number }, - ], - }), - ]; + describe('getVisualizationOptions2', () => { + it('should create an options list with the right number of categories and items', () => { + const vizPanel = new VizPanel({ + title: 'Panel A', + pluginId: 'timeseries', + key: 'panel-12', + }); - const panelData = { - series, - timeRange: getDefaultTimeRange(), - state: LoadingState.Done, + const property1: FieldConfigPropertyItem = { + id: 'custom.property1', // Match field properties + path: 'property1', // Match field properties + isCustom: true, + process: (value) => value, + shouldApply: () => true, + override: jest.fn(), + editor: jest.fn(), + name: 'Property 1', + }; + + const property2: FieldConfigPropertyItem = { + id: 'custom.property2', // Match field properties + path: 'property2', // Match field properties + isCustom: true, + process: (value) => value, + shouldApply: () => true, + override: jest.fn(), + editor: jest.fn(), + name: 'Property 2', + }; + + const property3: FieldConfigPropertyItem = { + id: 'custom.property3.nested', // Match field properties + path: 'property3.nested', // Match field properties + isCustom: true, + process: (value) => value, + shouldApply: () => true, + override: jest.fn(), + editor: jest.fn(), + name: 'Property 3', + }; + + const customFieldRegistry: FieldConfigOptionsRegistry = new Registry(() => { + return [property1, property2, property3]; + }); + + const plugin = { + meta: { skipDataQuery: false }, + getPanelOptionsSupplier: jest.fn, + fieldConfigRegistry: customFieldRegistry, + } as unknown as PanelPlugin; + + const vizOptions = getVisualizationOptions2({ + panel: vizPanel, + eventBus: new EventBusSrv(), + plugin: plugin, + instanceState: {}, + }); + + expect(vizOptions.length).toEqual(1); + expect(vizOptions[0].items.length).toEqual(3); + }); + + it('should not show items when the showIf evaluates to false', () => { + const vizPanel = new VizPanel({ + title: 'Panel A', + pluginId: 'timeseries', + key: 'panel-12', + }); + + const property1: FieldConfigPropertyItem = { + id: 'custom.property1', // Match field properties + path: 'property1', // Match field properties + isCustom: true, + process: (value) => value, + shouldApply: () => true, + override: jest.fn(), + editor: jest.fn(), + name: 'Property 1', + showIf: () => false, + }; + + const property2: FieldConfigPropertyItem = { + id: 'custom.property2', // Match field properties + path: 'property2', // Match field properties + isCustom: true, + process: (value) => value, + shouldApply: () => true, + override: jest.fn(), + editor: jest.fn(), + name: 'Property 2', + }; + + const property3: FieldConfigPropertyItem = { + id: 'custom.property3.nested', // Match field properties + path: 'property3.nested', // Match field properties + isCustom: true, + process: (value) => value, + shouldApply: () => true, + override: jest.fn(), + editor: jest.fn(), + name: 'Property 3', + }; + + const customFieldRegistry: FieldConfigOptionsRegistry = new Registry(() => { + return [property1, property2, property3]; + }); + + const plugin = { + meta: { skipDataQuery: false }, + getPanelOptionsSupplier: jest.fn, + fieldConfigRegistry: customFieldRegistry, + } as unknown as PanelPlugin; + + const vizOptions = getVisualizationOptions2({ + panel: vizPanel, + eventBus: new EventBusSrv(), + plugin: plugin, + instanceState: {}, + }); + + expect(vizOptions.length).toEqual(1); + expect(vizOptions[0].items.length).toEqual(2); + }); + + const fieldConfig = { + defaults: { + displayName: 'default', + custom: { + displayName: 'custom', + }, + }, + overrides: [], }; - const editorContext = getStandardEditorContext({ - data: panelData, - replaceVariables: jest.fn(), - options: {}, - eventBus: new EventBusSrv(), - instanceState: {}, + const vizPanel = new VizPanel({ + title: 'Panel A', + pluginId: 'timeseries', + key: 'panel-12', + fieldConfig: fieldConfig, }); - expect(editorContext.getSuggestions).toBeDefined(); - expect(editorContext.getSuggestions?.()).toEqual([ - { - documentation: 'Name of the series', - label: 'Name', - origin: 'series', - value: '__series.name', - }, - { - documentation: 'Field name of the clicked datapoint (in ms epoch)', - label: 'Name', - origin: 'field', - value: '__field.name', - }, - { - documentation: 'Formatted value for time on the same row', - label: 'time', - origin: 'fields', - value: '__data.fields.time', - }, - { - documentation: 'Formatted value for score on the same row', - label: 'score', - origin: 'fields', - value: '__data.fields.score', - }, - { - documentation: 'Enter the field order', - label: 'Select by index', - origin: 'fields', - value: '__data.fields[0]', - }, - { - documentation: 'the numeric field value', - label: 'Show numeric value', - origin: 'fields', - value: '__data.fields.score.numeric', - }, - { - documentation: 'the text value', - label: 'Show text value', - origin: 'fields', - value: '__data.fields.score.text', - }, - { - documentation: 'Adds current variables', - label: 'All variables', - origin: 'template', - value: '__all_variables', - }, - { - documentation: 'Adds current time range', - label: 'Time range', - origin: 'built-in', - value: '__url_time_range', - }, - { - documentation: "Adds current time range's from value", - label: 'Time range: from', - origin: 'built-in', - value: '__from', - }, - { - documentation: "Adds current time range's to value", - label: 'Time range: to', - origin: 'built-in', - value: '__to', - }, - ]); + const getOnePropVizPlugin = (isCustom: boolean, showIfSpy: jest.Mock) => { + const property1: FieldConfigPropertyItem = { + id: 'custom.property1', // Match field properties + path: 'property1', // Match field properties + isCustom: isCustom, + process: (value) => value, + shouldApply: () => true, + override: jest.fn(), + editor: jest.fn(), + name: 'Property 1', + showIf: showIfSpy, + }; + + const customFieldRegistry: FieldConfigOptionsRegistry = new Registry(() => { + return [property1]; + }); + + return { + meta: { skipDataQuery: false }, + getPanelOptionsSupplier: jest.fn, + fieldConfigRegistry: customFieldRegistry, + } as unknown as PanelPlugin; + }; + + it('showIf should get custom fieldConfig if isCustom is true', () => { + const showIfSpy = jest.fn().mockReturnValue(true); + + const plugin = getOnePropVizPlugin(true, showIfSpy); + + const vizOptions = getVisualizationOptions2({ + panel: vizPanel, + eventBus: new EventBusSrv(), + plugin: plugin, + instanceState: {}, + data: { + state: LoadingState.Done, + series: [], + timeRange: getDefaultTimeRange(), + annotations: [ + { + fields: [{ name: 'test', type: FieldType.string, config: { displayName: 'annotation' }, values: [1] }], + length: 1, + }, + ], + }, + }); + + expect(vizOptions.length).toEqual(1); + expect(vizOptions[0].items.length).toEqual(1); + expect(showIfSpy.mock.calls.length).toEqual(1); + expect(showIfSpy.mock.calls[0][0].displayName).toBe('custom'); + expect(showIfSpy.mock.calls[0][2][0].fields[0].config.displayName).toBe('annotation'); + }); + + it('showIf should get normal fieldConfig if isCustom is false', () => { + const showIfSpy = jest.fn().mockReturnValue(true); + + const plugin = getOnePropVizPlugin(false, showIfSpy); + + const vizOptions = getVisualizationOptions2({ + panel: vizPanel, + eventBus: new EventBusSrv(), + plugin: plugin, + instanceState: {}, + data: { + state: LoadingState.Done, + series: [], + timeRange: getDefaultTimeRange(), + annotations: [ + { + fields: [{ name: 'test', type: FieldType.string, config: { displayName: 'annotation' }, values: [1] }], + length: 1, + }, + ], + }, + }); + + expect(vizOptions.length).toEqual(1); + expect(vizOptions[0].items.length).toEqual(1); + expect(showIfSpy.mock.calls.length).toEqual(1); + expect(showIfSpy.mock.calls[0][0].displayName).toBe('default'); + expect(showIfSpy.mock.calls[0][2][0].fields[0].config.displayName).toBe('annotation'); + }); }); }); diff --git a/public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.tsx b/public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.tsx index be19274ca5d..b62d0493c2e 100644 --- a/public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.tsx +++ b/public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.tsx @@ -52,6 +52,7 @@ export function getStandardEditorContext({ eventBus, getSuggestions: (scope?: VariableSuggestionsScope) => getDataLinksVariableSuggestions(dataSeries, scope), instanceState, + annotations: data?.annotations, }; return context; @@ -102,11 +103,14 @@ export function getVisualizationOptions(props: OptionPaneRenderProps): OptionsPa */ for (const fieldOption of plugin.fieldConfigRegistry.list()) { if (fieldOption.isCustom) { - if (fieldOption.showIf && !fieldOption.showIf(currentFieldConfig.defaults.custom, data?.series)) { + if ( + fieldOption.showIf && + !fieldOption.showIf(currentFieldConfig.defaults.custom, data?.series, data?.annotations) + ) { continue; } } else { - if (fieldOption.showIf && !fieldOption.showIf(currentFieldConfig.defaults, data?.series)) { + if (fieldOption.showIf && !fieldOption.showIf(currentFieldConfig.defaults, data?.series, data?.annotations)) { continue; } } @@ -240,8 +244,8 @@ export function getVisualizationOptions2(props: OptionPaneRenderProps2): Options const hideOption = fieldOption.showIf && (fieldOption.isCustom - ? !fieldOption.showIf(currentFieldConfig.defaults.custom, data?.series) - : !fieldOption.showIf(currentFieldConfig.defaults, data?.series)); + ? !fieldOption.showIf(currentFieldConfig.defaults.custom, data?.series, data?.annotations) + : !fieldOption.showIf(currentFieldConfig.defaults, data?.series, data?.annotations)); if (fieldOption.hideFromDefaults || hideOption) { continue; } @@ -298,7 +302,7 @@ export function fillOptionsPaneItems( supplier(builder, context); for (const pluginOption of builder.getItems()) { - if (pluginOption.showIf && !pluginOption.showIf(context.options, context.data)) { + if (pluginOption.showIf && !pluginOption.showIf(context.options, context.data, context.annotations)) { continue; } diff --git a/public/app/features/datasources/components/EditDataSource.test.tsx b/public/app/features/datasources/components/EditDataSource.test.tsx index 378695c0506..be1736f77ee 100644 --- a/public/app/features/datasources/components/EditDataSource.test.tsx +++ b/public/app/features/datasources/components/EditDataSource.test.tsx @@ -1,8 +1,9 @@ import { screen, render } from '@testing-library/react'; import { Provider } from 'react-redux'; -import { PluginExtensionTypes, PluginState } from '@grafana/data'; -import { setAngularLoader, setPluginExtensionsHook } from '@grafana/runtime'; +import { PluginState } from '@grafana/data'; +import { setAngularLoader, setPluginComponentsHook } from '@grafana/runtime'; +import { createComponentWithMeta } from 'app/features/plugins/extensions/usePluginComponents'; import { configureStore } from 'app/store/configureStore'; import { getMockDataSource, getMockDataSourceMeta, getMockDataSourceSettingsState } from '../__mocks__'; @@ -58,7 +59,7 @@ describe('', () => { }); beforeEach(() => { - setPluginExtensionsHook(jest.fn().mockReturnValue({ extensions: [] })); + setPluginComponentsHook(jest.fn().mockReturnValue({ isLoading: false, components: [] })); }); describe('On loading errors', () => { @@ -268,17 +269,19 @@ describe('', () => { it('should be possible to extend the form with a "component" extension in case the plugin ID is whitelisted', () => { const message = "I'm a UI extension component!"; - setPluginExtensionsHook( + setPluginComponentsHook( jest.fn().mockReturnValue({ - extensions: [ - { - id: '1', - pluginId: 'grafana-pdc-app', - type: PluginExtensionTypes.component, - title: 'Example component', - description: 'Example description', - component: () =>
{message}
, - }, + isLoading: false, + components: [ + createComponentWithMeta( + { + pluginId: 'grafana-pdc-app', + title: 'Example component', + description: 'Example description', + component: () =>
{message}
, + }, + '1' + ), ], }) ); @@ -297,17 +300,19 @@ describe('', () => { it('should NOT be possible to extend the form with a "component" extension in case the plugin ID is NOT whitelisted', () => { const message = "I'm a UI extension component!"; - setPluginExtensionsHook( + setPluginComponentsHook( jest.fn().mockReturnValue({ - extensions: [ - { - id: '1', - pluginId: 'myorg-basic-app', - type: PluginExtensionTypes.component, - title: 'Example component', - description: 'Example description', - component: () =>
{message}
, - }, + isLoading: false, + components: [ + createComponentWithMeta( + { + pluginId: 'myorg-basic-app', + title: 'Example component', + description: 'Example description', + component: () =>
{message}
, + }, + '1' + ), ], }) ); @@ -327,17 +332,19 @@ describe('', () => { const message = "I'm a UI extension component!"; const component = jest.fn().mockReturnValue(
{message}
); - setPluginExtensionsHook( + setPluginComponentsHook( jest.fn().mockReturnValue({ - extensions: [ - { - id: '1', - pluginId: 'grafana-pdc-app', - type: PluginExtensionTypes.component, - title: 'Example component', - description: 'Example description', - component, - }, + isLoading: false, + components: [ + createComponentWithMeta( + { + pluginId: 'grafana-pdc-app', + title: 'Example component', + description: 'Example description', + component, + }, + '1' + ), ], }) ); diff --git a/public/app/features/datasources/components/EditDataSource.tsx b/public/app/features/datasources/components/EditDataSource.tsx index e948bad2013..7fe90eba063 100644 --- a/public/app/features/datasources/components/EditDataSource.tsx +++ b/public/app/features/datasources/components/EditDataSource.tsx @@ -9,10 +9,9 @@ import { DataSourceSettings as DataSourceSettingsType, PluginExtensionPoints, PluginExtensionDataSourceConfigContext, - DataSourceJsonData, DataSourceUpdatedSuccessfully, } from '@grafana/data'; -import { getDataSourceSrv, usePluginComponentExtensions } from '@grafana/runtime'; +import { getDataSourceSrv, usePluginComponents, UsePluginComponentsResult } from '@grafana/runtime'; import appEvents from 'app/core/app_events'; import PageLoader from 'app/core/components/PageLoader/PageLoader'; import { DataSourceSettingsState, useDispatch } from 'app/types'; @@ -118,6 +117,7 @@ export function EditDataSourceView({ const { plugin, loadError, testingStatus, loading } = dataSourceSettings; const { readOnly, hasWriteRights, hasDeleteRights } = dataSourceRights; const hasDataSource = dataSource.id > 0; + const { components, isLoading } = useDataSourceConfigPluginExtensions(); const dsi = getDataSourceSrv()?.getInstanceSettings(dataSource.uid); @@ -137,16 +137,6 @@ export function EditDataSourceView({ onTest(); }; - const extensionPointId = PluginExtensionPoints.DataSourceConfig; - const { extensions } = usePluginComponentExtensions<{ - context: PluginExtensionDataSourceConfigContext; - }>({ extensionPointId }); - - const allowedExtensions = useMemo(() => { - const allowedPluginIds = ['grafana-pdc-app', 'grafana-auth-app']; - return extensions.filter((e) => allowedPluginIds.includes(e.pluginId)); - }, [extensions]); - if (loadError) { return ( ; } @@ -204,11 +194,9 @@ export function EditDataSourceView({ )} {/* Extension point */} - {allowedExtensions.map((extension) => { - const Component = extension.component; - + {components.map((Component) => { return ( -
+
); } + +type DataSourceConfigPluginExtensionProps = { + context: PluginExtensionDataSourceConfigContext; +}; + +function useDataSourceConfigPluginExtensions(): UsePluginComponentsResult { + const { components, isLoading } = usePluginComponents({ + extensionPointId: PluginExtensionPoints.DataSourceConfig, + }); + + return useMemo(() => { + const allowedComponents = components.filter((component) => { + switch (component.meta.pluginId) { + case 'grafana-pdc-app': + case 'grafana-auth-app': + return true; + default: + return false; + } + }); + + return { components: allowedComponents, isLoading }; + }, [components, isLoading]); +} diff --git a/public/app/features/plugins/extensions/usePluginComponents.tsx b/public/app/features/plugins/extensions/usePluginComponents.tsx index d5259a85c41..c3e406d5d23 100644 --- a/public/app/features/plugins/extensions/usePluginComponents.tsx +++ b/public/app/features/plugins/extensions/usePluginComponents.tsx @@ -80,7 +80,8 @@ export function usePluginComponents({ }, [extensionPointId, limitPerPlugin, pluginContext, registryState, isLoadingAppPlugins]); } -function createComponentWithMeta( +// exported so it can be used in tests +export function createComponentWithMeta( registryItem: AddedComponentRegistryItem, extensionPointId: string ): React.ComponentType & { meta: PluginExtensionComponentMeta } { diff --git a/public/app/plugins/panel/nodeGraph/Edge.tsx b/public/app/plugins/panel/nodeGraph/Edge.tsx index 405d07f1a9e..e4802236cef 100644 --- a/public/app/plugins/panel/nodeGraph/Edge.tsx +++ b/public/app/plugins/panel/nodeGraph/Edge.tsx @@ -15,10 +15,11 @@ interface Props { onClick: (event: MouseEvent, link: EdgeDatumLayout) => void; onMouseEnter: (id: string) => void; onMouseLeave: (id: string) => void; + processedNodesLength: number; } export const Edge = memo(function Edge(props: Props) { - const { edge, onClick, onMouseEnter, onMouseLeave, hovering, svgIdNamespace } = props; + const { edge, onClick, onMouseEnter, onMouseLeave, hovering, svgIdNamespace, processedNodesLength } = props; // Not great typing but after we do layout these properties are full objects not just references const { source, target, sourceNodeRadius, targetNodeRadius } = edge as { @@ -56,6 +57,7 @@ export const Edge = memo(function Edge(props: Props) { onClick(event, edge)} style={{ cursor: 'pointer' }} aria-label={`Edge from: ${source.id} to: ${target.id}`} diff --git a/public/app/plugins/panel/nodeGraph/NodeGraph.tsx b/public/app/plugins/panel/nodeGraph/NodeGraph.tsx index 02a7f5018d6..ffd741ca52c 100644 --- a/public/app/plugins/panel/nodeGraph/NodeGraph.tsx +++ b/public/app/plugins/panel/nodeGraph/NodeGraph.tsx @@ -227,6 +227,7 @@ export function NodeGraph({ getLinks, dataFrames, nodeLimit, panelId, zoomMode } onMouseEnter={setEdgeHover} onMouseLeave={clearEdgeHover} svgIdNamespace={svgIdNamespace} + processedNodesLength={processed.nodes.length} /> )} , link: EdgeDatumLayout) => void; onMouseEnter: (id: string) => void; onMouseLeave: (id: string) => void; + processedNodesLength: number; } const Edges = memo(function Edges(props: EdgesProps) { return ( <> {props.edges.map((e) => ( ))} diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index 93ff994ba79..1222cec2022 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -1750,6 +1750,12 @@ "drawer": { "close": "Schließen" }, + "feature-badge": { + "experimental": "", + "new": "", + "preview": "", + "private-preview": "" + }, "field-link-list": { "external-links-heading": "" }, @@ -2756,7 +2762,6 @@ "close": "Menü schließen", "dock": "Menü andocken", "list-label": "Navigation", - "new": "", "open": "", "undock": "Menü abdocken" }, diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 1ffbdf68fc0..d650a4aa8c1 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -1750,6 +1750,12 @@ "drawer": { "close": "Close" }, + "feature-badge": { + "experimental": "Experimental", + "new": "New!", + "preview": "Preview", + "private-preview": "Private preview" + }, "field-link-list": { "external-links-heading": "External links" }, @@ -2756,7 +2762,6 @@ "close": "Close menu", "dock": "Dock menu", "list-label": "Navigation", - "new": "New!", "open": "Open menu", "undock": "Undock menu" }, diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index a7f1f3424f4..e6d9efb5f9b 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -1750,6 +1750,12 @@ "drawer": { "close": "Cerrar" }, + "feature-badge": { + "experimental": "", + "new": "", + "preview": "", + "private-preview": "" + }, "field-link-list": { "external-links-heading": "" }, @@ -2756,7 +2762,6 @@ "close": "Cerrar menú", "dock": "Anclar el menú", "list-label": "Navegación", - "new": "", "open": "", "undock": "Desanclar el menú" }, diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index b7d309f71b5..f6ab1a8be74 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -1750,6 +1750,12 @@ "drawer": { "close": "Fermer" }, + "feature-badge": { + "experimental": "", + "new": "", + "preview": "", + "private-preview": "" + }, "field-link-list": { "external-links-heading": "" }, @@ -2756,7 +2762,6 @@ "close": "Fermer le menu", "dock": "Ancrer le menu", "list-label": "Navigation", - "new": "", "open": "", "undock": "Ancrer le menu" }, diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 255bfa497b3..a3094aea301 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -1750,6 +1750,12 @@ "drawer": { "close": "Cľőşę" }, + "feature-badge": { + "experimental": "Ēχpęřįmęʼnŧäľ", + "new": "Ńęŵ!", + "preview": "Přęvįęŵ", + "private-preview": "Přįväŧę přęvįęŵ" + }, "field-link-list": { "external-links-heading": "Ēχŧęřʼnäľ ľįʼnĸş" }, @@ -2756,7 +2762,6 @@ "close": "Cľőşę męʼnū", "dock": "Đőčĸ męʼnū", "list-label": "Ńävįģäŧįőʼn", - "new": "Ńęŵ!", "open": "Øpęʼn męʼnū", "undock": "Ůʼnđőčĸ męʼnū" }, diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index 32cddf19af3..c10c3c0eaa4 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -1750,6 +1750,12 @@ "drawer": { "close": "Fechar" }, + "feature-badge": { + "experimental": "", + "new": "", + "preview": "", + "private-preview": "" + }, "field-link-list": { "external-links-heading": "" }, @@ -2756,7 +2762,6 @@ "close": "Fechar menu", "dock": "Menu da dock", "list-label": "Navegação", - "new": "", "open": "", "undock": "Desacoplar menu" }, diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index f51ef704205..eb52a53913b 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -1741,6 +1741,12 @@ "drawer": { "close": "关闭" }, + "feature-badge": { + "experimental": "", + "new": "", + "preview": "", + "private-preview": "" + }, "field-link-list": { "external-links-heading": "" }, @@ -2746,7 +2752,6 @@ "close": "关闭菜单", "dock": "停靠菜单", "list-label": "导航", - "new": "", "open": "", "undock": "取消停靠菜单" }, diff --git a/public/openapi3.json b/public/openapi3.json index e5be6cd60ce..fabc90f92cf 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -12838,7 +12838,6 @@ "type": "object" }, "gettableAlerts": { - "description": "GettableAlerts gettable alerts", "items": { "$ref": "#/components/schemas/gettableAlert" }, @@ -12962,7 +12961,6 @@ "type": "object" }, "gettableSilences": { - "description": "GettableSilences gettable silences", "items": { "$ref": "#/components/schemas/gettableSilence" }, diff --git a/scripts/drone/rgm.star b/scripts/drone/rgm.star index f81effc892a..37b4fad4f70 100644 --- a/scripts/drone/rgm.star +++ b/scripts/drone/rgm.star @@ -19,6 +19,7 @@ load( load( "scripts/drone/steps/github.star", "github_app_generate_token_step", + "github_app_pipeline_volumes", "github_app_step_volumes", ) load( @@ -350,7 +351,7 @@ def rgm_promotion_pipeline(): "--grafana-ref=$${GRAFANA_REF} " + "--enterprise-ref=$${ENTERPRISE_REF} " + "--grafana-repo=$${GRAFANA_REPO} " + - "--version=$${VERSION} ", + "--version=$${VERSION} " + "--go-version={}".format(golang_version), ], "environment": rgm_env_secrets(env), @@ -364,6 +365,11 @@ def rgm_promotion_pipeline(): build_step["depends_on"] = [ generate_token_step["name"], ] + + publish_step["depends_on"] = [ + build_step["name"], + ] + steps = [ generate_token_step, build_step, @@ -375,7 +381,7 @@ def rgm_promotion_pipeline(): name = "rgm-promotion", trigger = promotion_trigger, steps = steps, - volumes = github_app_step_volumes(), + volumes = github_app_step_volumes() + github_app_pipeline_volumes(), ), ] diff --git a/yarn.lock b/yarn.lock index e6c2c660e45..eb8360924ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4080,7 +4080,7 @@ __metadata: react-highlight-words: "npm:0.21.0" react-hook-form: "npm:^7.49.2" react-i18next: "npm:^15.0.0" - react-inlinesvg: "npm:4.1.5" + react-inlinesvg: "npm:4.2.0" react-loading-skeleton: "npm:3.5.0" react-router-dom: "npm:5.3.4" react-router-dom-v5-compat: "npm:^6.26.1" @@ -14872,15 +14872,15 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.3.7, debug@npm:~4.3.1, debug@npm:~4.3.2, debug@npm:~4.3.4": - version: 4.3.7 - resolution: "debug@npm:4.3.7" +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.3.7, debug@npm:^4.4.0": + version: 4.4.0 + resolution: "debug@npm:4.4.0" dependencies: ms: "npm:^2.1.3" peerDependenciesMeta: supports-color: optional: true - checksum: 10/71168908b9a78227ab29d5d25fe03c5867750e31ce24bf2c44a86efc5af041758bb56569b0a3d48a9b5344c00a24a777e6f4100ed6dfd9534a42c1dde285125a + checksum: 10/1847944c2e3c2c732514b93d11886575625686056cd765336212dc15de2d2b29612b6cd80e1afba767bb8e1803b778caf9973e98169ef1a24a7a7009e1820367 languageName: node linkType: hard @@ -14893,15 +14893,15 @@ __metadata: languageName: node linkType: hard -"debug@npm:^4.4.0": - version: 4.4.0 - resolution: "debug@npm:4.4.0" +"debug@npm:~4.3.1, debug@npm:~4.3.2, debug@npm:~4.3.4": + version: 4.3.7 + resolution: "debug@npm:4.3.7" dependencies: ms: "npm:^2.1.3" peerDependenciesMeta: supports-color: optional: true - checksum: 10/1847944c2e3c2c732514b93d11886575625686056cd765336212dc15de2d2b29612b6cd80e1afba767bb8e1803b778caf9973e98169ef1a24a7a7009e1820367 + checksum: 10/71168908b9a78227ab29d5d25fe03c5867750e31ce24bf2c44a86efc5af041758bb56569b0a3d48a9b5344c00a24a777e6f4100ed6dfd9534a42c1dde285125a languageName: node linkType: hard @@ -14922,20 +14922,13 @@ __metadata: languageName: node linkType: hard -"decimal.js@npm:10": +"decimal.js@npm:10, decimal.js@npm:^10.4.1": version: 10.5.0 resolution: "decimal.js@npm:10.5.0" checksum: 10/714d49cf2f2207b268221795ede330e51452b7c451a0c02a770837d2d4faed47d603a729c2aa1d952eb6c4102d999e91c9b952c1aa016db3c5cba9fc8bf4cda2 languageName: node linkType: hard -"decimal.js@npm:^10.4.1": - version: 10.4.3 - resolution: "decimal.js@npm:10.4.3" - checksum: 10/de663a7bc4d368e3877db95fcd5c87b965569b58d16cdc4258c063d231ca7118748738df17cd638f7e9dd0be8e34cec08d7234b20f1f2a756a52fc5a38b188d0 - languageName: node - linkType: hard - "decode-uri-component@npm:^0.2.0": version: 0.2.2 resolution: "decode-uri-component@npm:0.2.2" @@ -17740,7 +17733,7 @@ __metadata: languageName: node linkType: hard -"get-tsconfig@npm:^4.10.0": +"get-tsconfig@npm:^4.10.0, get-tsconfig@npm:^4.7.0": version: 4.10.0 resolution: "get-tsconfig@npm:4.10.0" dependencies: @@ -17749,15 +17742,6 @@ __metadata: languageName: node linkType: hard -"get-tsconfig@npm:^4.7.0": - version: 4.8.1 - resolution: "get-tsconfig@npm:4.8.1" - dependencies: - resolve-pkg-maps: "npm:^1.0.0" - checksum: 10/3fb5a8ad57b9633eaea085d81661e9e5c9f78b35d8f8689eaf8b8b45a2a3ebf3b3422266d4d7df765e308cc1e6231648d114803ab3d018332e29916f2c1de036 - languageName: node - linkType: hard - "get-user-locale@npm:^2.2.1": version: 2.3.0 resolution: "get-user-locale@npm:2.3.0" @@ -18384,7 +18368,7 @@ __metadata: react-highlight-words: "npm:0.21.0" react-hook-form: "npm:^7.49.2" react-i18next: "npm:^15.0.0" - react-inlinesvg: "npm:4.1.5" + react-inlinesvg: "npm:4.2.0" react-loading-skeleton: "npm:3.5.0" react-moveable: "npm:0.56.0" react-redux: "npm:9.2.0" @@ -26372,12 +26356,12 @@ __metadata: languageName: node linkType: hard -"react-from-dom@npm:^0.7.3": - version: 0.7.3 - resolution: "react-from-dom@npm:0.7.3" +"react-from-dom@npm:^0.7.5": + version: 0.7.5 + resolution: "react-from-dom@npm:0.7.5" peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 10/55d6365af5b2aeaa0f2d80808dfa96114367e63849821b7ea277d193d4f6b1fce020e9754d4527ebc7f8628c5333f19dae38fc7b7956f69d5f640ac28b37ab0f + react: 16.8 - 19 + checksum: 10/57459e775b2e2a12f3fc6bcc5365b88505ae856bd82188edd9e3b15c6318295316071789d06666533ee89ba4ccfd43ec7a5c68fbf4fbcaccb9e495eb02961947 languageName: node linkType: hard @@ -26528,14 +26512,14 @@ __metadata: languageName: node linkType: hard -"react-inlinesvg@npm:4.1.5": - version: 4.1.5 - resolution: "react-inlinesvg@npm:4.1.5" +"react-inlinesvg@npm:4.2.0": + version: 4.2.0 + resolution: "react-inlinesvg@npm:4.2.0" dependencies: - react-from-dom: "npm:^0.7.3" + react-from-dom: "npm:^0.7.5" peerDependencies: react: 16.8 - 19 - checksum: 10/475666855056007bfec56968d61793530f2089997d8a2956d603e03d9da8cd353a00368f220f07ef554c65feb4f5839112cefb2c84b22e511d8a470bea8eee61 + checksum: 10/cf55657efce21c4891ab5875800722cf0184017bc821606ddc3775561c47e0648da1956bc4af14e13e18139d206af9eb834d7601098ff7b37caad7d010e99c2d languageName: node linkType: hard