From 5a8384a2455bbd3c0ba5ec67e5f5e3cc4a836904 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Fri, 19 Apr 2024 12:26:21 +0300 Subject: [PATCH 01/17] QueryService: Add feature toggles to better support testing (#86493) --- .../feature-toggles/index.md | 4 +- .../src/types/featureToggles.gen.ts | 4 +- packages/grafana-runtime/src/config.ts | 31 +++----- .../src/utils/DataSourceWithBackend.ts | 12 +++ pkg/api/{metrics.go => ds_query.go} | 6 +- pkg/api/{metrics_test.go => ds_query_test.go} | 0 pkg/registry/apis/query/README.md | 78 +++++++++++++++++++ pkg/registry/apis/query/client/plugin.go | 2 +- pkg/registry/apis/query/plugins.go | 9 ++- pkg/registry/apis/query/register.go | 15 +++- pkg/services/featuremgmt/registry.go | 17 +++- pkg/services/featuremgmt/toggles_gen.csv | 4 +- pkg/services/featuremgmt/toggles_gen.go | 12 ++- pkg/services/featuremgmt/toggles_gen.json | 39 +++++++++- 14 files changed, 191 insertions(+), 42 deletions(-) rename pkg/api/{metrics.go => ds_query.go} (94%) rename pkg/api/{metrics_test.go => ds_query_test.go} (100%) create mode 100644 pkg/registry/apis/query/README.md diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 16f94bbdef4..4fd4ca7182f 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -150,6 +150,9 @@ Experimental features might be changed or removed without prior notice. | `idForwarding` | Generate signed id token for identity that can be forwarded to plugins and external services | | `enableNativeHTTPHistogram` | Enables native HTTP Histograms | | `kubernetesSnapshots` | Routes snapshot requests from /api to the /apis endpoint | +| `queryService` | Register /apis/query.grafana.app/ -- will eventually replace /api/ds/query | +| `queryServiceRewrite` | Rewrite requests targeting /ds/query to the query service | +| `queryServiceFromUI` | Routes requests to the new query service | | `cachingOptimizeSerializationMemoryUsage` | If enabled, the caching backend gradually serializes query responses for the cache, comparing against the configured `[caching]max_value_mb` value as it goes. This can can help prevent Grafana from running out of memory while attempting to cache very large query responses. | | `prometheusPromQAIL` | Prometheus and AI/ML to assist users in creating a query | | `prometheusCodeModeMetricNamesSearch` | Enables search for metric names in Code Mode, to improve performance when working with an enormous number of metric names | @@ -184,5 +187,4 @@ The following toggles require explicitly setting Grafana's [app mode]({{< relref | `unifiedStorage` | SQL-based k8s storage | | `grafanaAPIServerWithExperimentalAPIs` | Register experimental APIs with the k8s API server | | `grafanaAPIServerEnsureKubectlAccess` | Start an additional https handler and write kubectl options | -| `kubernetesQueryServiceRewrite` | Rewrite requests targeting /ds/query to the query service | | `panelTitleSearchInV1` | Enable searching for dashboards using panel title in search v1 | diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index ee22f554a83..3ac4413419c 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -121,7 +121,9 @@ export interface FeatureToggles { transformationsVariableSupport?: boolean; kubernetesPlaylists?: boolean; kubernetesSnapshots?: boolean; - kubernetesQueryServiceRewrite?: boolean; + queryService?: boolean; + queryServiceRewrite?: boolean; + queryServiceFromUI?: boolean; cloudWatchBatchQueries?: boolean; recoveryThreshold?: boolean; lokiStructuredMetadata?: boolean; diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index 624174f13d9..82fb397c7ab 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -214,9 +214,7 @@ export class GrafanaBootConfig implements GrafanaConfig { systemDateFormats.update(this.dateFormats); } - if (this.buildInfo.env === 'development') { - overrideFeatureTogglesFromUrl(this); - } + overrideFeatureTogglesFromUrl(this); overrideFeatureTogglesFromLocalStorage(this); if (this.featureToggles.disableAngular) { @@ -253,15 +251,11 @@ function overrideFeatureTogglesFromUrl(config: GrafanaBootConfig) { return; } - const migrationFeatureFlags = new Set([ - 'autoMigrateOldPanels', - 'autoMigrateGraphPanel', - 'autoMigrateTablePanel', - 'autoMigratePiechartPanel', - 'autoMigrateWorldmapPanel', - 'autoMigrateStatPanel', - 'disableAngular', - ]); + const isDevelopment = config.buildInfo.env === 'development'; + + // Although most flags can not be changed from the URL in production, + // some of them are safe (and useful!) to change dynamically from the browser URL + const safeRuntimeFeatureFlags = new Set(['queryServiceFromUI']); const params = new URLSearchParams(window.location.search); params.forEach((value, key) => { @@ -269,15 +263,14 @@ function overrideFeatureTogglesFromUrl(config: GrafanaBootConfig) { const featureToggles = config.featureToggles as Record; const featureName = key.substring(10); - // skip the migration feature flags - if (migrationFeatureFlags.has(featureName)) { - return; - } - const toggleState = value === 'true' || value === ''; // browser rewrites true as '' if (toggleState !== featureToggles[key]) { - featureToggles[featureName] = toggleState; - console.log(`Setting feature toggle ${featureName} = ${toggleState} via url`); + if (isDevelopment || safeRuntimeFeatureFlags.has(featureName)) { + featureToggles[featureName] = toggleState; + console.log(`Setting feature toggle ${featureName} = ${toggleState} via url`); + } else { + console.log(`Unable to change feature toggle ${featureName} via url in production.`); + } } } }); diff --git a/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts b/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts index f867d1762cf..77deaa5c797 100644 --- a/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts +++ b/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts @@ -207,6 +207,18 @@ class DataSourceWithBackend< let url = '/api/ds/query?ds_type=' + this.type; + // Use the new query service + if (config.featureToggles.queryServiceFromUI) { + if (!(config.featureToggles.queryService || config.featureToggles.grafanaAPIServerWithExperimentalAPIs)) { + console.warn('feature toggle queryServiceFromUI also requires the queryService to be running'); + } else { + if (!hasExpr && dsUIDs.size === 1) { + // TODO? can we talk directly to the apiserver? + } + url = `/apis/query.grafana.app/v0alpha1/namespaces/${config.namespace}/query?ds_type=' + this.type`; + } + } + if (hasExpr) { headers[PluginRequestHeaders.FromExpression] = 'true'; url += '&expression=true'; diff --git a/pkg/api/metrics.go b/pkg/api/ds_query.go similarity index 94% rename from pkg/api/metrics.go rename to pkg/api/ds_query.go index 010ca3cb8e2..bac9342383f 100644 --- a/pkg/api/metrics.go +++ b/pkg/api/ds_query.go @@ -7,7 +7,6 @@ import ( "net/http" "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana/pkg/util/proxyutil" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" @@ -40,8 +39,7 @@ func (hs *HTTPServer) handleQueryMetricsError(err error) *response.NormalRespons // metrics.go func (hs *HTTPServer) getDSQueryEndpoint() web.Handler { - if hs.Features.IsEnabledGlobally(featuremgmt.FlagKubernetesQueryServiceRewrite) { - // DEV ONLY FEATURE FLAG! + if hs.Features.IsEnabledGlobally(featuremgmt.FlagQueryServiceRewrite) { // rewrite requests from /ds/query to the new query service namespaceMapper := request.GetNamespaceMapper(hs.Cfg) return func(w http.ResponseWriter, r *http.Request) { @@ -51,11 +49,9 @@ func (hs *HTTPServer) getDSQueryEndpoint() web.Handler { return } r.URL.Path = "/apis/query.grafana.app/v0alpha1/namespaces/" + namespaceMapper(user.OrgID) + "/query" - r.Header.Add(proxyutil.IDHeaderName, user.GetIDToken()) hs.clientConfigProvider.DirectlyServeHTTP(w, r) } } - return routing.Wrap(hs.QueryMetricsV2) } diff --git a/pkg/api/metrics_test.go b/pkg/api/ds_query_test.go similarity index 100% rename from pkg/api/metrics_test.go rename to pkg/api/ds_query_test.go diff --git a/pkg/registry/apis/query/README.md b/pkg/registry/apis/query/README.md new file mode 100644 index 00000000000..e8d3ba64a7c --- /dev/null +++ b/pkg/registry/apis/query/README.md @@ -0,0 +1,78 @@ +# Query service + +This query service aims to replace the existing /api/ds/query. + +The key differences are: +1. This service has a stronger type system (not simplejson) +2. Same workflow regardless if expressions exist +3. Datasource settings+access is managed in each datasource, not at the beginning + + + +### Current /api/ds/query workflow + +```mermaid +sequenceDiagram + autonumber + actor User as User or Process + participant api as /api/ds/query + participant db as Storage
(SQL) + participant ds as Datasource
Plugin + participant expr as Expression
Engine + + User->>api: POST Query + loop Each query + api->>api: Parse query + api->>db: Get ds config
and secrets + db->>api: + end + alt No expressions + alt Single datasource + api->>ds: QueryData + else Multiple datasources + loop Each datasource (concurrently) + api->>ds: QueryData + end + api->>api: Wait for results + end + else Expressions exist + api->>expr: Calculate expressions graph + loop Each node (eg, refID) + alt Is query + expr->>ds: QueryData + else Is expression + expr->>expr: Process + end + end + end + api->>User: return results +``` + + + +### /apis/query.grafana.app (in single tenant grafana) + +```mermaid +sequenceDiagram + autonumber + actor User as User or Process + participant api as /apis/query.grafana.app + participant ds as Datasource
Handler/Plugin + participant db as Storage
(SQL) + participant expr as Expression
Engine + + User->>api: POST Query + api->>api: Parse queries + api->>api: Calculate dependencies + loop Each datasource (concurrently) + api->>ds: QueryData + ds->>ds: Verify user access + ds->>db: Get settings
and secrets + end + loop Each expression + api->>expr: Execute + end + api->>api: Verify ResultExpectations + api->>User: return results +``` + diff --git a/pkg/registry/apis/query/client/plugin.go b/pkg/registry/apis/query/client/plugin.go index a5354e35ecf..6e842f7ac45 100644 --- a/pkg/registry/apis/query/client/plugin.go +++ b/pkg/registry/apis/query/client/plugin.go @@ -40,7 +40,7 @@ type pluginRegistry struct { var _ data.QueryDataClient = (*pluginClient)(nil) var _ query.DataSourceApiServerRegistry = (*pluginRegistry)(nil) -// NewDummyTestRunner creates a runner that only works with testdata +// NewQueryClientForPluginClient creates a client that delegates to the internal plugins.Client stack func NewQueryClientForPluginClient(p plugins.Client, ctx *plugincontext.Provider) data.QueryDataClient { return &pluginClient{ pluginClient: p, diff --git a/pkg/registry/apis/query/plugins.go b/pkg/registry/apis/query/plugins.go index 1836602d4de..6c4a4861df9 100644 --- a/pkg/registry/apis/query/plugins.go +++ b/pkg/registry/apis/query/plugins.go @@ -9,7 +9,6 @@ import ( "k8s.io/apiserver/pkg/registry/rest" common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" - example "github.com/grafana/grafana/pkg/apis/example/v0alpha1" query "github.com/grafana/grafana/pkg/apis/query/v0alpha1" ) @@ -24,6 +23,9 @@ type pluginsStorage struct { resourceInfo *common.ResourceInfo tableConverter rest.TableConvertor registry query.DataSourceApiServerRegistry + + // Always return an empty list regardless what we think exists + returnEmptyList bool } func newPluginsStorage(reg query.DataSourceApiServerRegistry) *pluginsStorage { @@ -46,7 +48,7 @@ func (s *pluginsStorage) NamespaceScoped() bool { } func (s *pluginsStorage) GetSingularName() string { - return example.DummyResourceInfo.GetSingularName() + return s.resourceInfo.GetSingularName() } func (s *pluginsStorage) NewList() runtime.Object { @@ -58,5 +60,8 @@ func (s *pluginsStorage) ConvertToTable(ctx context.Context, object runtime.Obje } func (s *pluginsStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { + if s.returnEmptyList { + return s.NewList(), nil + } return s.registry.GetDatasourceApiServers(ctx) } diff --git a/pkg/registry/apis/query/register.go b/pkg/registry/apis/query/register.go index 1b6460df3e8..a3892beec47 100644 --- a/pkg/registry/apis/query/register.go +++ b/pkg/registry/apis/query/register.go @@ -85,8 +85,9 @@ func RegisterAPIService(features featuremgmt.FeatureToggles, tracer tracing.Tracer, legacy service.LegacyDataSourceLookup, ) (*QueryAPIBuilder, error) { - if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) { - return nil, nil // skip registration unless opting into experimental apis + if !(features.IsEnabledGlobally(featuremgmt.FlagQueryService) || + features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs)) { + return nil, nil // skip registration unless explicitly added (or all experimental are added) } builder, err := NewQueryAPIBuilder( @@ -132,10 +133,16 @@ func (b *QueryAPIBuilder) GetAPIGroupInfo( gv := v0alpha1.SchemeGroupVersion apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(gv.Group, scheme, metav1.ParameterCodec, codecs) - plugins := newPluginsStorage(b.registry) - storage := map[string]rest.Storage{} + + plugins := newPluginsStorage(b.registry) storage[plugins.resourceInfo.StoragePath()] = plugins + if !b.features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) { + // The plugin registry is still experimental, and not yet accurate + // For standard k8s api discovery to work, at least one resource must be registered + // While this feature is under development, we can return an empty list for non-dev instances + plugins.returnEmptyList = true + } apiGroupInfo.VersionedResourcesStorageMap[gv.Version] = storage return &apiGroupInfo, nil diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 9ecc6f8cf26..7bc464263bd 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -769,12 +769,25 @@ var ( RequiresRestart: true, // changes the API routing }, { - Name: "kubernetesQueryServiceRewrite", + Name: "queryService", + Description: "Register /apis/query.grafana.app/ -- will eventually replace /api/ds/query", + Stage: FeatureStageExperimental, + Owner: grafanaAppPlatformSquad, + RequiresRestart: true, // Adds a route at startup + }, + { + Name: "queryServiceRewrite", Description: "Rewrite requests targeting /ds/query to the query service", Stage: FeatureStageExperimental, Owner: grafanaAppPlatformSquad, RequiresRestart: true, // changes the API routing - RequiresDevMode: true, + }, + { + Name: "queryServiceFromUI", + Description: "Routes requests to the new query service", + Stage: FeatureStageExperimental, + Owner: grafanaAppPlatformSquad, + FrontendOnly: true, // and can change at startup }, { Name: "cloudWatchBatchQueries", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 6714b969fc1..e227e488de6 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -102,7 +102,9 @@ formatString,preview,@grafana/dataviz-squad,false,false,true transformationsVariableSupport,preview,@grafana/dataviz-squad,false,false,true kubernetesPlaylists,GA,@grafana/grafana-app-platform-squad,false,true,false kubernetesSnapshots,experimental,@grafana/grafana-app-platform-squad,false,true,false -kubernetesQueryServiceRewrite,experimental,@grafana/grafana-app-platform-squad,true,true,false +queryService,experimental,@grafana/grafana-app-platform-squad,false,true,false +queryServiceRewrite,experimental,@grafana/grafana-app-platform-squad,false,true,false +queryServiceFromUI,experimental,@grafana/grafana-app-platform-squad,false,false,true cloudWatchBatchQueries,preview,@grafana/aws-datasources,false,false,false recoveryThreshold,GA,@grafana/alerting-squad,false,true,false lokiStructuredMetadata,GA,@grafana/observability-logs,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 0b387c29b96..34bd5c41dd5 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -419,9 +419,17 @@ const ( // Routes snapshot requests from /api to the /apis endpoint FlagKubernetesSnapshots = "kubernetesSnapshots" - // FlagKubernetesQueryServiceRewrite + // FlagQueryService + // Register /apis/query.grafana.app/ -- will eventually replace /api/ds/query + FlagQueryService = "queryService" + + // FlagQueryServiceRewrite // Rewrite requests targeting /ds/query to the query service - FlagKubernetesQueryServiceRewrite = "kubernetesQueryServiceRewrite" + FlagQueryServiceRewrite = "queryServiceRewrite" + + // FlagQueryServiceFromUI + // Routes requests to the new query service + FlagQueryServiceFromUI = "queryServiceFromUI" // FlagCloudWatchBatchQueries // Runs CloudWatch metrics queries as separate batches diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 0f30ede4d7b..b123aa03f33 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -278,15 +278,17 @@ }, { "metadata": { - "name": "kubernetesQueryServiceRewrite", - "resourceVersion": "1712639261786", - "creationTimestamp": "2024-04-09T05:07:41Z" + "name": "queryServiceRewrite", + "resourceVersion": "1713422970838", + "creationTimestamp": "2024-04-09T05:07:41Z", + "annotations": { + "grafana.app/updatedTimestamp": "2024-04-18 06:49:30.838977 +0000 UTC" + } }, "spec": { "description": "Rewrite requests targeting /ds/query to the query service", "stage": "experimental", "codeowner": "@grafana/grafana-app-platform-squad", - "requiresDevMode": true, "requiresRestart": true } }, @@ -2134,6 +2136,35 @@ "codeowner": "@grafana/observability-metrics", "requiresRestart": true } + }, + { + "metadata": { + "name": "queryServiceFromUI", + "resourceVersion": "1713422970838", + "creationTimestamp": "2024-04-18T06:49:30Z" + }, + "spec": { + "description": "Routes requests to the new query service", + "stage": "experimental", + "codeowner": "@grafana/grafana-app-platform-squad", + "frontend": true + } + }, + { + "metadata": { + "name": "queryService", + "resourceVersion": "1713504737045", + "creationTimestamp": "2024-04-18T06:49:30Z", + "annotations": { + "grafana.app/updatedTimestamp": "2024-04-19 05:32:17.045343 +0000 UTC" + } + }, + "spec": { + "description": "Register /apis/query.grafana.app/ -- will eventually replace /api/ds/query", + "stage": "experimental", + "codeowner": "@grafana/grafana-app-platform-squad", + "requiresRestart": true + } } ] } \ No newline at end of file From 73873f5a8aca25b440d9d467d8d6568fe8537950 Mon Sep 17 00:00:00 2001 From: Steve Simpson Date: Fri, 19 Apr 2024 11:51:22 +0200 Subject: [PATCH 02/17] Alerting: Optimize rule status gathering APIs when a limit is applied. (#86568) * Alerting: Optimize rule status gathering APIs when a limit is applied. The frontend very commonly calls the `/rules` API with `limit_alerts=16`. When there are a very large number of alert instances present, this API is quite slow to respond, and profiling suggests that a big part of the problem is sorting the alerts by importance, in order to select the first 16. This changes the application of the limit to use a more efficient heap-based top-k algorithm. This maintains a slice of only the highest ranked items whilst iterating the full set of alert instances, which substantially reduces the number of comparisons needed. This is particularly effective, as the `AlertsByImportance` comparison is quite complex. I've included a benchmark to compare the new TopK function to the existing Sort/limit strategy. It shows that for small limits, the new approach is much faster, especially at high numbers of alerts, e.g. 100K alerts / limit 16: 1.91s vs 0.02s (-99%) For situations where there is no effective limit, sorting is marginally faster, therefore in the API implementation, if there is either a) no limit or b) no effective limit, then we just sort the alerts as before. There is also a space overhead using a heap which would matter for large limits. * Remove commented test cases * Make linter happy --- pkg/services/ngalert/api/api_prometheus.go | 8 +- .../ngalert/api/tooling/definitions/prom.go | 66 ++++++++++++++ .../api/tooling/definitions/prom_bench.sh | 5 ++ .../tooling/definitions/prom_bench_test.go | 87 +++++++++++++++++++ .../api/tooling/definitions/prom_test.go | 48 ++++++++++ 5 files changed, 212 insertions(+), 2 deletions(-) create mode 100755 pkg/services/ngalert/api/tooling/definitions/prom_bench.sh create mode 100644 pkg/services/ngalert/api/tooling/definitions/prom_bench_test.go diff --git a/pkg/services/ngalert/api/api_prometheus.go b/pkg/services/ngalert/api/api_prometheus.go index 883dcf57724..5c1211f659d 100644 --- a/pkg/services/ngalert/api/api_prometheus.go +++ b/pkg/services/ngalert/api/api_prometheus.go @@ -408,10 +408,14 @@ func (srv PrometheusSrv) toRuleGroup(groupKey ngmodels.AlertRuleGroupKey, folder rulesTotals[newRule.Health] += 1 } - apimodels.AlertsBy(apimodels.AlertsByImportance).Sort(alertingRule.Alerts) + alertsBy := apimodels.AlertsBy(apimodels.AlertsByImportance) if limitAlerts > -1 && int64(len(alertingRule.Alerts)) > limitAlerts { - alertingRule.Alerts = alertingRule.Alerts[0:limitAlerts] + alertingRule.Alerts = alertsBy.TopK(alertingRule.Alerts, int(limitAlerts)) + } else { + // If there is no effective limit, then just sort the alerts. + // For large numbers of alerts, this can be faster. + alertsBy.Sort(alertingRule.Alerts) } alertingRule.Rule = newRule diff --git a/pkg/services/ngalert/api/tooling/definitions/prom.go b/pkg/services/ngalert/api/tooling/definitions/prom.go index 43ae592d329..c97d94d770d 100644 --- a/pkg/services/ngalert/api/tooling/definitions/prom.go +++ b/pkg/services/ngalert/api/tooling/definitions/prom.go @@ -1,6 +1,7 @@ package definitions import ( + "container/heap" "fmt" "sort" "strings" @@ -206,6 +207,71 @@ func (by AlertsBy) Sort(alerts []Alert) { sort.Sort(AlertsSorter{alerts: alerts, by: by}) } +// AlertsHeap extends AlertsSorter for use with container/heap functions. +type AlertsHeap struct { + AlertsSorter +} + +func (h *AlertsHeap) Push(x any) { + h.alerts = append(h.alerts, x.(Alert)) +} + +func (h *AlertsHeap) Pop() any { + old := h.alerts + n := len(old) + x := old[n-1] + h.alerts = old[0 : n-1] + return x +} + +// TopK returns the highest k elements. It does not modify the input. +func (by AlertsBy) TopK(alerts []Alert, k int) []Alert { + // Concept is that instead of sorting the whole list and taking the number + // of items we need, maintain a heap of the top k elements, and update it + // for each element. This vastly reduces the number of comparisons needed, + // which is important for sorting alerts, as the comparison function is + // very expensive. + + // The heap must be in ascending order, so that the root of the heap is + // the current smallest element. + byAscending := func(a1, a2 *Alert) bool { return by(a2, a1) } + + h := AlertsHeap{ + AlertsSorter: AlertsSorter{ + alerts: make([]Alert, 0, k), + by: byAscending, + }, + } + + // Go version of this algorithm taken from Prometheus (promql/engine.go) + + heap.Init(&h) + for i := 0; i < len(alerts); i++ { + a := alerts[i] + + // We build a heap of up to k elements, with the smallest element at heap[0]. + switch { + case len(h.alerts) < k: + heap.Push(&h, a) + + case h.by(&h.alerts[0], &a): + // This new element is bigger than the previous smallest element - overwrite that. + h.alerts[0] = a + // Maintain the heap invariant. + if k > 1 { + heap.Fix(&h, 0) + } + } + } + + // The heap keeps the lowest value on top, so reverse it. + if len(h.alerts) > 1 { + sort.Sort(sort.Reverse(&h)) + } + + return h.alerts +} + // AlertsByImportance orders alerts by importance. An alert is more important // than another alert if its status has higher importance. For example, "alerting" // is more important than "normal". If two alerts have the same importance diff --git a/pkg/services/ngalert/api/tooling/definitions/prom_bench.sh b/pkg/services/ngalert/api/tooling/definitions/prom_bench.sh new file mode 100755 index 00000000000..fadf371a29a --- /dev/null +++ b/pkg/services/ngalert/api/tooling/definitions/prom_bench.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +go test -v -run=^# -bench BenchmarkSortAlertsByImportance -count 5 -topk sort | tee before.txt +go test -v -run=^# -bench BenchmarkSortAlertsByImportance -count 5 -topk heap | tee after.txt +benchstat before.txt after.txt diff --git a/pkg/services/ngalert/api/tooling/definitions/prom_bench_test.go b/pkg/services/ngalert/api/tooling/definitions/prom_bench_test.go new file mode 100644 index 00000000000..64a051751cc --- /dev/null +++ b/pkg/services/ngalert/api/tooling/definitions/prom_bench_test.go @@ -0,0 +1,87 @@ +package definitions + +import ( + "flag" + "fmt" + "math/rand" + "testing" +) + +var topkStrategy = flag.String("topk", "heap", "topk strategy to benchmark. choices: sort, heap") +var showComparisons = flag.Bool("show-comparisons", false, "whether to show the number of comparisons made") + +func makeAlerts(amount int) []Alert { + // A typical distribution of alert states is that most are Normal + // and a few are Alerting, so we assume 99% Normal and 1% Alerting. + percentAlerting := 1 + + // Series will commonly have many labels. + numLabels := 10 + + alerts := make([]Alert, amount) + + for i := 0; i < len(alerts); i++ { + alerts[i].Labels = make(map[string]string) + for label := 0; label < numLabels; label++ { + alerts[i].Labels[fmt.Sprintf("label_%d", label)] = fmt.Sprintf("label_%d_value_%d", label, i%100) + } + + if i%100 < percentAlerting { + alerts[i].State = "alerting" + // Should populate ActiveAt because this prevents needing label comparison + } else { + alerts[i].State = "normal" + } + } + + // Shuffle in a repeatable order to avoid any bias from the initial ordering. + r := rand.New(rand.NewSource(1)) + r.Shuffle(len(alerts), func(i, j int) { alerts[i], alerts[j] = alerts[j], alerts[i] }) + + return alerts +} + +func BenchmarkSortAlertsByImportance(b *testing.B) { + var topkFunc func(AlertsBy, []Alert, int) + + switch *topkStrategy { + case "sort": + topkFunc = func(by AlertsBy, alerts []Alert, limit int) { + by.Sort(alerts) + if len(alerts) > limit { + _ = alerts[0:limit] + } + } + + case "heap": + topkFunc = func(by AlertsBy, alerts []Alert, limit int) { + _ = by.TopK(alerts, limit) + } + } + + for _, n := range []int{1000, 10000, 100000} { + for _, k := range []int{16, 100, 1000, 100000} { + b.Run(fmt.Sprintf("n_%d_k_%d", n, k), func(b *testing.B) { + b.StopTimer() + + for bi := 0; bi < b.N; bi++ { + alerts := makeAlerts(n) + + comparisons := 0 + by := func(a1, a2 *Alert) bool { + comparisons++ + return AlertsByImportance(a1, a2) + } + + b.StartTimer() + topkFunc(by, alerts, k) + b.StopTimer() + + if *showComparisons { + fmt.Printf("Number of comparisons (strategy: %s): %d\n", *topkStrategy, comparisons) + } + } + }) + } + } +} diff --git a/pkg/services/ngalert/api/tooling/definitions/prom_test.go b/pkg/services/ngalert/api/tooling/definitions/prom_test.go index 107eedcb5c2..5546cd374f1 100644 --- a/pkg/services/ngalert/api/tooling/definitions/prom_test.go +++ b/pkg/services/ngalert/api/tooling/definitions/prom_test.go @@ -64,3 +64,51 @@ func TestSortAlertsByImportance(t *testing.T) { }) } } + +func TestTopKAlertsByImportance(t *testing.T) { + // tm1, tm2 := time.Now(), time.Now().Add(time.Second) + tc := []struct { + name string + k int + input []Alert + expected []Alert + }{{ + name: "alerts are ordered in expected importance (k=1)", + k: 1, + input: []Alert{{State: "normal"}, {State: "nodata"}, {State: "error"}, {State: "pending"}, {State: "alerting"}}, + expected: []Alert{{State: "alerting"}}, + }, { + name: "alerts are ordered in expected importance (k=2)", + k: 2, + input: []Alert{{State: "normal"}, {State: "nodata"}, {State: "error"}, {State: "pending"}, {State: "alerting"}}, + expected: []Alert{{State: "alerting"}, {State: "pending"}}, + }, { + name: "alerts are ordered in expected importance (k=3)", + k: 3, + input: []Alert{{State: "normal"}, {State: "nodata"}, {State: "error"}, {State: "pending"}, {State: "alerting"}}, + expected: []Alert{{State: "alerting"}, {State: "pending"}, {State: "error"}}, + }, { + name: "alerts are ordered in expected importance (k=4)", + k: 4, + input: []Alert{{State: "normal"}, {State: "nodata"}, {State: "error"}, {State: "pending"}, {State: "alerting"}}, + expected: []Alert{{State: "alerting"}, {State: "pending"}, {State: "error"}, {State: "nodata"}}, + }, { + name: "alerts are ordered in expected importance (k=5)", + k: 5, + input: []Alert{{State: "normal"}, {State: "nodata"}, {State: "error"}, {State: "pending"}, {State: "alerting"}}, + expected: []Alert{{State: "alerting"}, {State: "pending"}, {State: "error"}, {State: "nodata"}, {State: "normal"}}, + }, { + name: "alerts are ordered in expected importance (k=6)", + k: 6, + input: []Alert{{State: "normal"}, {State: "nodata"}, {State: "error"}, {State: "pending"}, {State: "alerting"}}, + expected: []Alert{{State: "alerting"}, {State: "pending"}, {State: "error"}, {State: "nodata"}, {State: "normal"}}, + }, + } + + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + result := AlertsBy(AlertsByImportance).TopK(tt.input, tt.k) + assert.EqualValues(t, tt.expected, result) + }) + } +} From f9a8e34b32eac0cac7aec294ac3b97b9b3c1fe2f Mon Sep 17 00:00:00 2001 From: ismail simsek Date: Fri, 19 Apr 2024 11:54:56 +0200 Subject: [PATCH 03/17] Prometheus: Update lezer-promql package (#85942) * Update @lezer/lr to v1.4.0 * Update @prometheus-io/lezer-promql to v0.37.0 * Update @prometheus-io/lezer-promql to v0.38.0 * Update @prometheus-io/lezer-promql to v0.39.0 * Update @prometheus-io/lezer-promql to v0.40.0 * add jest config * update code * fix code to pass "handles things" test * fix retrieving labels * fix code to pass "handles label values" test * fix code to pass "simple binary comparison" test * use BoolModifier * add changed lines as comments * fix for ambiguous query parsing tests * resolve rebase conflict * fix retrieving labels, aggregation with/out labels * add error * fix comment * fix "reports error on parenthesis" unit test * fix for "handles binary operation with vector matchers" test * fix for "handles multiple binary scalar operations" test * fix for "parses query without metric" test * fix indentation and import style * remove commented lines * add todo items and comments * remove dependency update from tempo datasource * apply same changes in core prometheus frontend * prettier * add new test case * use old version of lezer in the root package.json * Revert "apply same changes in core prometheus frontend" This reverts commit 83fd6ac7 * fix indentation * use latest version of lezer-promql v0.51.2 * Update packages/grafana-prometheus/src/querybuilder/parsing.ts Co-authored-by: Nick Richmond <5732000+NWRichmond@users.noreply.github.com> * enable native histogram test --------- Co-authored-by: Nick Richmond <5732000+NWRichmond@users.noreply.github.com> --- packages/grafana-prometheus/package.json | 4 +- .../situation.test.ts | 13 ++ .../monaco-completion-provider/situation.ts | 116 ++++-------------- .../src/querybuilder/parsing.test.ts | 21 +++- .../src/querybuilder/parsing.ts | 80 +++++------- .../src/querybuilder/parsingUtils.ts | 7 +- yarn.lock | 29 ++++- 7 files changed, 114 insertions(+), 156 deletions(-) diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index 004fcd9764d..bafc259fcb9 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -47,8 +47,8 @@ "@leeoniya/ufuzzy": "1.0.14", "@lezer/common": "1.2.1", "@lezer/highlight": "1.2.0", - "@lezer/lr": "1.3.3", - "@prometheus-io/lezer-promql": "^0.37.0-rc.1", + "@lezer/lr": "1.4.0", + "@prometheus-io/lezer-promql": "0.51.2", "@reduxjs/toolkit": "1.9.5", "d3": "7.9.0", "date-fns": "3.6.0", diff --git a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/situation.test.ts b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/situation.test.ts index 605d9658174..188eb333415 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/situation.test.ts +++ b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/situation.test.ts @@ -183,4 +183,17 @@ describe('situation', () => { ], }); }); + + it('identifies all labels from queries when cursor is in middle', () => { + // Note the extra whitespace, if the cursor is after whitespace, the situation will fail to resolve + assertSituation('{one="val1", ^,two!="val2",three=~"val3",four!~"val4"}', { + type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME', + otherLabels: [ + { name: 'one', value: 'val1', op: '=' }, + { name: 'two', value: 'val2', op: '!=' }, + { name: 'three', value: 'val3', op: '=~' }, + { name: 'four', value: 'val4', op: '!~' }, + ], + }); + }); }); diff --git a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/situation.ts b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/situation.ts index f633922d298..decb3c2c9ac 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/situation.ts +++ b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/situation.ts @@ -3,6 +3,7 @@ import type { SyntaxNode, Tree } from '@lezer/common'; import { AggregateExpr, AggregateModifier, + BinaryExpr, EqlRegex, EqlSingle, FunctionCallBody, @@ -10,11 +11,9 @@ import { Identifier, LabelMatcher, LabelMatchers, - LabelMatchList, LabelName, MatchOp, MatrixSelector, - MetricIdentifier, Neq, NeqRegex, parser, @@ -36,9 +35,7 @@ type NodeTypeId = | typeof Identifier | typeof LabelMatcher | typeof LabelMatchers - | typeof LabelMatchList | typeof LabelName - | typeof MetricIdentifier | typeof PromQL | typeof StringLiteral | typeof VectorSelector @@ -184,6 +181,10 @@ const RESOLVERS: Resolver[] = [ path: [StringLiteral, LabelMatcher], fun: resolveLabelMatcher, }, + { + path: [ERROR_NODE_NAME, BinaryExpr, PromQL], + fun: resolveTopLevel, + }, { path: [ERROR_NODE_NAME, LabelMatcher], fun: resolveLabelMatcher, @@ -252,30 +253,8 @@ function getLabels(labelMatchersNode: SyntaxNode, text: string): Label[] { return []; } - let listNode: SyntaxNode | null = walk(labelMatchersNode, [['firstChild', LabelMatchList]]); - - const labels: Label[] = []; - - while (listNode !== null) { - const matcherNode = walk(listNode, [['lastChild', LabelMatcher]]); - if (matcherNode === null) { - // unexpected, we stop - return []; - } - - const label = getLabel(matcherNode, text); - if (label !== null) { - labels.push(label); - } - - // there might be more labels - listNode = walk(listNode, [['firstChild', LabelMatchList]]); - } - - // our labels-list is last-first, so we reverse it - labels.reverse(); - - return labels; + const labelNodes = labelMatchersNode.getChildren(LabelMatcher); + return labelNodes.map((ln) => getLabel(ln, text)).filter(notEmpty); } function getNodeChildren(node: SyntaxNode): SyntaxNode[] { @@ -319,17 +298,12 @@ function resolveLabelsForGrouping(node: SyntaxNode, text: string, pos: number): return null; } - const metricIdNode = getNodeInSubtree(bodyNode, MetricIdentifier); + const metricIdNode = getNodeInSubtree(bodyNode, Identifier); if (metricIdNode === null) { return null; } - const idNode = walk(metricIdNode, [['firstChild', Identifier]]); - if (idNode === null) { - return null; - } - - const metricName = getNodeText(idNode, text); + const metricName = getNodeText(metricIdNode, text); return { type: 'IN_GROUPING', metricName, @@ -355,44 +329,11 @@ function resolveLabelMatcher(node: SyntaxNode, text: string, pos: number): Situa const labelName = getNodeText(labelNameNode, text); - // now we need to go up, to the parent of LabelMatcher, - // there can be one or many `LabelMatchList` parents, we have - // to go through all of them - - const firstListNode = walk(parent, [['parent', LabelMatchList]]); - if (firstListNode === null) { + const labelMatchersNode = walk(parent, [['parent', LabelMatchers]]); + if (labelMatchersNode === null) { return null; } - let listNode = firstListNode; - - // we keep going through the parent-nodes - // as long as they are LabelMatchList. - // as soon as we reawch LabelMatchers, we stop - let labelMatchersNode: SyntaxNode | null = null; - while (labelMatchersNode === null) { - const p = listNode.parent; - if (p === null) { - return null; - } - - const { id } = p.type; - - switch (id) { - case LabelMatchList: - //we keep looping - listNode = p; - continue; - case LabelMatchers: - // we reached the end, we can stop the loop - labelMatchersNode = p; - continue; - default: - // we reached some other node, we stop - return null; - } - } - // now we need to find the other names const allLabels = getLabels(labelMatchersNode, text); @@ -401,7 +342,6 @@ function resolveLabelMatcher(node: SyntaxNode, text: string, pos: number): Situa const metricNameNode = walk(labelMatchersNode, [ ['parent', VectorSelector], - ['firstChild', MetricIdentifier], ['firstChild', Identifier], ]); @@ -444,23 +384,10 @@ function resolveDurations(node: SyntaxNode, text: string, pos: number): Situatio }; } -function subTreeHasError(node: SyntaxNode): boolean { - return getNodeInSubtree(node, ERROR_NODE_NAME) !== null; -} - function resolveLabelKeysWithEquals(node: SyntaxNode, text: string, pos: number): Situation | null { - // for example `something{^}` - - // there are some false positives that can end up in this situation, that we want - // to eliminate: - // `something{a~^}` (if this subtree contains any error-node, we stop) - if (subTreeHasError(node)) { - return null; - } - // next false positive: // `something{a="1"^}` - const child = walk(node, [['firstChild', LabelMatchList]]); + const child = walk(node, [['firstChild', LabelMatcher]]); if (child !== null) { // means the label-matching part contains at least one label already. // @@ -477,7 +404,6 @@ function resolveLabelKeysWithEquals(node: SyntaxNode, text: string, pos: number) const metricNameNode = walk(node, [ ['parent', VectorSelector], - ['firstChild', MetricIdentifier], ['firstChild', Identifier], ]); @@ -533,12 +459,12 @@ export function getSituation(text: string, pos: number): Situation | null { }; } - /* - PromQL - Expr - VectorSelector - LabelMatchers - */ + /** + PromQL + Expr + VectorSelector + LabelMatchers + */ const tree = parser.parse(text); // if the tree contains error, it is very probable that @@ -546,7 +472,6 @@ export function getSituation(text: string, pos: number): Situation | null { // also, if there are errors, the node lezer finds us, // might not be the best node. // so first we check if there is an error-node at the cursor-position - // @ts-ignore const maybeErrorNode = getErrorNode(tree, pos); const cur = maybeErrorNode != null ? maybeErrorNode.cursor() : tree.cursorAt(pos); @@ -561,10 +486,13 @@ export function getSituation(text: string, pos: number): Situation | null { // i do not use a foreach because i want to stop as soon // as i find something if (isPathMatch(resolver.path, ids)) { - // @ts-ignore return resolver.fun(currentNode, text, pos); } } return null; } + +function notEmpty(value: TValue | null | undefined): value is TValue { + return value !== null && value !== undefined; +} diff --git a/packages/grafana-prometheus/src/querybuilder/parsing.test.ts b/packages/grafana-prometheus/src/querybuilder/parsing.test.ts index 1522aff295f..3bbb8d32aeb 100644 --- a/packages/grafana-prometheus/src/querybuilder/parsing.test.ts +++ b/packages/grafana-prometheus/src/querybuilder/parsing.test.ts @@ -12,6 +12,7 @@ describe('buildVisualQueryFromString', () => { }) ); }); + it('parses simple binary comparison', () => { expect(buildVisualQueryFromString('{app="aggregator"} == 11')).toEqual({ query: { @@ -56,6 +57,7 @@ describe('buildVisualQueryFromString', () => { errors: [], }); }); + it('parses simple query', () => { expect(buildVisualQueryFromString('counters_logins{app="frontend"}')).toEqual( noErrors({ @@ -87,6 +89,7 @@ describe('buildVisualQueryFromString', () => { ], }); }); + it('throws error when visual query parse with aggregation is ambiguous (scalar)', () => { expect(buildVisualQueryFromString('topk(5, 1 / 2)')).toMatchObject({ errors: [ @@ -98,6 +101,7 @@ describe('buildVisualQueryFromString', () => { ], }); }); + it('throws error when visual query parse with functionCall is ambiguous', () => { expect( buildVisualQueryFromString( @@ -113,6 +117,7 @@ describe('buildVisualQueryFromString', () => { ], }); }); + it('does not throw error when visual query parse is unambiguous', () => { expect( buildVisualQueryFromString('topk(5, node_arp_entries) / node_arp_entries{cluster="dev-eu-west-2"}') @@ -120,12 +125,14 @@ describe('buildVisualQueryFromString', () => { errors: [], }); }); + it('does not throw error when visual query parse is unambiguous (scalar)', () => { // Note this topk query with scalars is not valid in prometheus, but it does not currently throw an error during parse expect(buildVisualQueryFromString('topk(5, 1) / 2')).toMatchObject({ errors: [], }); }); + it('does not throw error when visual query parse is unambiguous, function call', () => { // Note this topk query with scalars is not valid in prometheus, but it does not currently throw an error during parse expect( @@ -291,8 +298,7 @@ describe('buildVisualQueryFromString', () => { }); }); - // enable in #85942 when updated lezer parser is merged - xit('parses a native histogram function correctly', () => { + it('parses a native histogram function correctly', () => { expect( buildVisualQueryFromString('histogram_count(rate(counters_logins{app="backend"}[$__rate_interval]))') ).toEqual({ @@ -306,7 +312,8 @@ describe('buildVisualQueryFromString', () => { params: ['$__rate_interval'], }, { - id: 'histogram_quantile', + id: 'histogram_count', + params: [], }, ], }, @@ -457,6 +464,12 @@ describe('buildVisualQueryFromString', () => { to: 27, parentType: 'VectorSelector', }, + { + text: ')', + from: 38, + to: 39, + parentType: 'PromQL', + }, ], query: { metric: '${func_var}', @@ -710,7 +723,7 @@ describe('buildVisualQueryFromString', () => { errors: [ { from: 6, - parentType: 'Expr', + parentType: 'BinaryExpr', text: '(bar + baz)', to: 17, }, diff --git a/packages/grafana-prometheus/src/querybuilder/parsing.ts b/packages/grafana-prometheus/src/querybuilder/parsing.ts index af31365c564..01fd244012e 100644 --- a/packages/grafana-prometheus/src/querybuilder/parsing.ts +++ b/packages/grafana-prometheus/src/querybuilder/parsing.ts @@ -5,22 +5,18 @@ import { AggregateModifier, AggregateOp, BinaryExpr, - BinModifiers, - Expr, + BoolModifier, FunctionCall, - FunctionCallArgs, FunctionCallBody, FunctionIdentifier, - GroupingLabel, - GroupingLabelList, GroupingLabels, + Identifier, LabelMatcher, LabelName, + MatchingModifierClause, MatchOp, - MetricIdentifier, NumberLiteral, On, - OnOrIgnoring, ParenExpr, parser, StringLiteral, @@ -102,6 +98,7 @@ interface Context { errors: ParsingError[]; } +// TODO find a better approach for grafana global variables function isValidPromQLMinusGrafanaGlobalVariables(expr: string) { const context: Context = { query: { @@ -142,7 +139,7 @@ export function handleExpression(expr: string, node: SyntaxNode, context: Contex const visQuery = context.query; switch (node.type.id) { - case MetricIdentifier: { + case Identifier: { // Expectation is that there is only one of those per query. visQuery.metric = getString(expr, node); break; @@ -183,8 +180,8 @@ export function handleExpression(expr: string, node: SyntaxNode, context: Contex default: { if (node.type.id === ParenExpr) { - // We don't support parenthesis in the query to group expressions. We just report error but go on with the - // parsing. + // We don't support parenthesis in the query to group expressions. + // We just report error but go on with the parsing. context.errors.push(makeError(expr, node)); } // Any other nodes we just ignore and go to its children. This should be fine as there are lots of wrapper @@ -200,8 +197,9 @@ export function handleExpression(expr: string, node: SyntaxNode, context: Contex } } +// TODO check if we still need this function isIntervalVariableError(node: SyntaxNode) { - return node.prevSibling?.type.id === Expr && node.prevSibling?.firstChild?.type.id === VectorSelector; + return node.prevSibling?.firstChild?.type.id === VectorSelector; } function getLabel(expr: string, node: SyntaxNode): QueryBuilderLabelFilter { @@ -229,7 +227,6 @@ function handleFunction(expr: string, node: SyntaxNode, context: Context) { const funcName = getString(expr, nameNode); const body = node.getChild(FunctionCallBody); - const callArgs = body!.getChild(FunctionCallArgs); const params = []; let interval = ''; @@ -249,13 +246,13 @@ function handleFunction(expr: string, node: SyntaxNode, context: Context) { // We unshift operations to keep the more natural order that we want to have in the visual query editor. visQuery.operations.unshift(op); - if (callArgs) { - if (getString(expr, callArgs) === interval + ']') { + if (body) { + if (getString(expr, body) === '([' + interval + '])') { // This is a special case where we have a function with a single argument and it is the interval. // This happens when you start adding operations in query builder and did not set a metric yet. return; } - updateFunctionArgs(expr, callArgs, context, op); + updateFunctionArgs(expr, body, context, op); } } @@ -284,25 +281,14 @@ function handleAggregation(expr: string, node: SyntaxNode, context: Context) { funcName = `__${funcName}_without`; } - labels.push(...getAllByType(expr, modifier, GroupingLabel)); + labels.push(...getAllByType(expr, modifier, LabelName)); } const body = node.getChild(FunctionCallBody); - const callArgs = body!.getChild(FunctionCallArgs); - const callArgsExprChild = callArgs?.getChild(Expr); - const binaryExpressionWithinAggregationArgs = callArgsExprChild?.getChild(BinaryExpr); - - if (binaryExpressionWithinAggregationArgs) { - context.errors.push({ - text: 'Query parsing is ambiguous.', - from: binaryExpressionWithinAggregationArgs.from, - to: binaryExpressionWithinAggregationArgs.to, - }); - } const op: QueryBuilderOperation = { id: funcName, params: [] }; visQuery.operations.unshift(op); - updateFunctionArgs(expr, callArgs, context, op); + updateFunctionArgs(expr, body, context, op); // We add labels after params in the visual query editor. op.params.push(...labels); } @@ -310,8 +296,7 @@ function handleAggregation(expr: string, node: SyntaxNode, context: Context) { /** * Handle (probably) all types of arguments that function or aggregation can have. * - * FunctionCallArgs are nested bit weirdly basically its [firstArg, ...rest] where rest is again FunctionCallArgs so - * we cannot just get all the children and iterate them as arguments we have to again recursively traverse through + * We cannot just get all the children and iterate them as arguments we have to again recursively traverse through * them. * * @param expr @@ -324,15 +309,16 @@ function updateFunctionArgs(expr: string, node: SyntaxNode | null, context: Cont return; } switch (node.type.id) { - // In case we have an expression we don't know what kind so we have to look at the child as it can be anything. - case Expr: - // FunctionCallArgs are nested bit weirdly as mentioned so we have to go one deeper in this case. - case FunctionCallArgs: { + case FunctionCallBody: { let child = node.firstChild; while (child) { - const callArgsExprChild = child.getChild(Expr); - const binaryExpressionWithinFunctionArgs = callArgsExprChild?.getChild(BinaryExpr); + let binaryExpressionWithinFunctionArgs: SyntaxNode | null; + if (child.type.id === BinaryExpr) { + binaryExpressionWithinFunctionArgs = child; + } else { + binaryExpressionWithinFunctionArgs = child.getChild(BinaryExpr); + } if (binaryExpressionWithinFunctionArgs) { context.errors.push({ @@ -345,7 +331,6 @@ function updateFunctionArgs(expr: string, node: SyntaxNode | null, context: Cont updateFunctionArgs(expr, child, context, op); child = child.nextSibling; } - break; } @@ -378,16 +363,16 @@ function handleBinary(expr: string, node: SyntaxNode, context: Context) { const visQuery = context.query; const left = node.firstChild!; const op = getString(expr, left.nextSibling); - const binModifier = getBinaryModifier(expr, node.getChild(BinModifiers)); + const binModifier = getBinaryModifier(expr, node.getChild(BoolModifier) ?? node.getChild(MatchingModifierClause)); const right = node.lastChild!; const opDef = binaryScalarOperatorToOperatorName[op]; - const leftNumber = left.getChild(NumberLiteral); - const rightNumber = right.getChild(NumberLiteral); + const leftNumber = left.type.id === NumberLiteral; + const rightNumber = right.type.id === NumberLiteral; - const rightBinary = right.getChild(BinaryExpr); + const rightBinary = right.type.id === BinaryExpr; if (leftNumber) { // TODO: this should be already handled in case parent is binary expression as it has to be added to parent @@ -433,6 +418,7 @@ function handleBinary(expr: string, node: SyntaxNode, context: Context) { } } +// TODO revisit this function. function getBinaryModifier( expr: string, node: SyntaxNode | null @@ -446,17 +432,17 @@ function getBinaryModifier( if (node.getChild('Bool')) { return { isBool: true, isMatcher: false }; } else { - const matcher = node.getChild(OnOrIgnoring); - if (!matcher) { - // Not sure what this could be, maybe should be an error. - return undefined; + let labels = ''; + const groupingLabels = node.getChild(GroupingLabels); + if (groupingLabels) { + labels = getAllByType(expr, groupingLabels, LabelName).join(', '); } - const labels = getString(expr, matcher.getChild(GroupingLabels)?.getChild(GroupingLabelList)); + return { isMatcher: true, isBool: false, matches: labels, - matchType: matcher.getChild(On) ? 'on' : 'ignoring', + matchType: node.getChild(On) ? 'on' : 'ignoring', }; } } diff --git a/packages/grafana-prometheus/src/querybuilder/parsingUtils.ts b/packages/grafana-prometheus/src/querybuilder/parsingUtils.ts index bc19084122f..2b9cb162d93 100644 --- a/packages/grafana-prometheus/src/querybuilder/parsingUtils.ts +++ b/packages/grafana-prometheus/src/querybuilder/parsingUtils.ts @@ -114,11 +114,10 @@ export function makeBinOp( * not be safe is it would also find arguments of nested functions. * @param expr * @param cur - * @param type - can be string or number, some data-sources (loki) haven't migrated over to using numeric constants defined in the lezer parsing library (e.g. lezer-promql). - * @todo Remove string type definition when all data-sources have migrated to numeric constants + * @param type */ -export function getAllByType(expr: string, cur: SyntaxNode, type: number | string): string[] { - if (cur.type.id === type || cur.name === type) { +export function getAllByType(expr: string, cur: SyntaxNode, type: number): string[] { + if (cur.type.id === type) { return [getString(expr, cur)]; } const values: string[] = []; diff --git a/yarn.lock b/yarn.lock index 39e99715e8d..46c60360e89 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4012,8 +4012,8 @@ __metadata: "@leeoniya/ufuzzy": "npm:1.0.14" "@lezer/common": "npm:1.2.1" "@lezer/highlight": "npm:1.2.0" - "@lezer/lr": "npm:1.3.3" - "@prometheus-io/lezer-promql": "npm:^0.37.0-rc.1" + "@lezer/lr": "npm:1.4.0" + "@prometheus-io/lezer-promql": "npm:0.51.2" "@reduxjs/toolkit": "npm:1.9.5" "@rollup/plugin-image": "npm:3.0.3" "@rollup/plugin-node-resolve": "npm:15.2.3" @@ -5041,6 +5041,15 @@ __metadata: languageName: node linkType: hard +"@lezer/lr@npm:1.4.0": + version: 1.4.0 + resolution: "@lezer/lr@npm:1.4.0" + dependencies: + "@lezer/common": "npm:^1.0.0" + checksum: 10/7391d0d08e54cd9e4f4d46e6ee6aa81fbaf079b22ed9c13d01fc9928e0ffd16d0c2d21b2cedd55675ad6c687277db28349ea8db81c9c69222cd7e7c40edd026e + languageName: node + linkType: hard + "@linaria/core@npm:^4.5.4": version: 4.5.4 resolution: "@linaria/core@npm:4.5.4" @@ -6152,13 +6161,23 @@ __metadata: languageName: node linkType: hard +"@prometheus-io/lezer-promql@npm:0.51.2": + version: 0.51.2 + resolution: "@prometheus-io/lezer-promql@npm:0.51.2" + peerDependencies: + "@lezer/highlight": ^1.1.2 + "@lezer/lr": ^1.2.3 + checksum: 10/cee04e8bb24b54caa5da029ab66aade5245c8ed96a99ca2444b45a1a814dc03e01197e4b4d9dd767baa9f81c35441c879939e13517b5fd5854598ceb58087e6b + languageName: node + linkType: hard + "@prometheus-io/lezer-promql@npm:^0.37.0-rc.1": - version: 0.37.0 - resolution: "@prometheus-io/lezer-promql@npm:0.37.0" + version: 0.37.9 + resolution: "@prometheus-io/lezer-promql@npm:0.37.9" peerDependencies: "@lezer/highlight": ^1.0.0 "@lezer/lr": ^1.0.0 - checksum: 10/00a3ef7a292ae17c7059da73e1ebd4568135eb5189be0eb60f039915f1c20a0bf355fe02cec1c11955e9e3885b5ecfdd8a67d57ce25fa09ad74575ba0fbc7386 + checksum: 10/3b1ddd9b47e3ba4f016901d6fc1b3b7b75855fb5da568fb95b30bfc60d35065e89d64162d947312126163a314c8844fa4a72176f9babdf86c63837d3fc0a5e4a languageName: node linkType: hard From 60e6dd56bff055aaa3e8288da21fbc16955bc2a2 Mon Sep 17 00:00:00 2001 From: Victor Marin <36818606+mdvictor@users.noreply.github.com> Date: Fri, 19 Apr 2024 13:21:40 +0300 Subject: [PATCH 04/17] Change folder breadcrumb on folder change in general settings (#86342) * Change folder breadcrumb on folder change in general settings * tests and refactor * refactor to fix broken tests * fix test --- .../pages/DashboardScenePageStateManager.ts | 24 +++----------- .../dashboard-scene/pages/utils.test.ts | 32 +++++++++++++++++++ .../features/dashboard-scene/pages/utils.ts | 13 ++++++++ .../settings/GeneralSettingsEditView.test.tsx | 8 +++-- .../settings/GeneralSettingsEditView.tsx | 7 +++- 5 files changed, 62 insertions(+), 22 deletions(-) create mode 100644 public/app/features/dashboard-scene/pages/utils.test.ts create mode 100644 public/app/features/dashboard-scene/pages/utils.ts diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts index e878af98383..c9132a9415e 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts @@ -1,8 +1,6 @@ import { locationUtil } from '@grafana/data'; import { config, getBackendSrv, isFetchError, locationService } from '@grafana/runtime'; -import { updateNavIndex } from 'app/core/actions'; import { StateManagerBase } from 'app/core/services/StateManagerBase'; -import { backendSrv } from 'app/core/services/backend_srv'; import { default as localStorageStore } from 'app/core/store'; import { dashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; @@ -10,8 +8,6 @@ import { DASHBOARD_FROM_LS_KEY, removeDashboardToFetchFromLocalStorage, } from 'app/features/dashboard/state/initDashboard'; -import { buildNavModel } from 'app/features/folders/state/navModel'; -import { store } from 'app/store/store'; import { DashboardDTO, DashboardRoutes } from 'app/types'; import { PanelEditor } from '../panel-edit/PanelEditor'; @@ -19,6 +15,8 @@ import { DashboardScene } from '../scene/DashboardScene'; import { buildNewDashboardSaveModel } from '../serialization/buildNewDashboardSaveModel'; import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene'; +import { updateNavModel } from './utils'; + export interface DashboardScenePageState { dashboard?: DashboardScene; panelEditor?: PanelEditor; @@ -127,7 +125,9 @@ export class DashboardScenePageStateManager extends StateManagerBase { + it('Should update nav model', async () => { + const reduxStore = configureStore(); + + jest.spyOn(backendSrv, 'getFolderByUid').mockResolvedValue({ + id: 1, + uid: 'new-folder', + title: 'NewFolder', + url: '', + canAdmin: true, + canDelete: true, + canEdit: true, + canSave: true, + created: '', + createdBy: '', + hasAcl: false, + updated: '', + updatedBy: '', + }); + + expect(reduxStore.getState().navIndex[`folder-dashboards-new-folder`]).toBeUndefined(); + + await updateNavModel('new-folder'); + + expect(reduxStore.getState().navIndex[`folder-dashboards-new-folder`]).not.toBeUndefined(); + }); +}); diff --git a/public/app/features/dashboard-scene/pages/utils.ts b/public/app/features/dashboard-scene/pages/utils.ts new file mode 100644 index 00000000000..2e06429589f --- /dev/null +++ b/public/app/features/dashboard-scene/pages/utils.ts @@ -0,0 +1,13 @@ +import { updateNavIndex } from 'app/core/actions'; +import { backendSrv } from 'app/core/services/backend_srv'; +import { buildNavModel } from 'app/features/folders/state/navModel'; +import { store } from 'app/store/store'; + +export async function updateNavModel(folderUid: string) { + try { + const folder = await backendSrv.getFolderByUid(folderUid); + store.dispatch(updateNavIndex(buildNavModel(folder))); + } catch (err) { + console.warn('Error fetching parent folder', folderUid, 'for dashboard', err); + } +} diff --git a/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.test.tsx b/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.test.tsx index e1e55da7e44..e2f343c383c 100644 --- a/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.test.tsx +++ b/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.test.tsx @@ -1,6 +1,7 @@ import { behaviors, SceneGridLayout, SceneTimeRange } from '@grafana/scenes'; import { DashboardCursorSync } from '@grafana/schema'; +import * as utils from '../pages/utils'; import { DashboardControls } from '../scene/DashboardControls'; import { DashboardScene } from '../scene/DashboardScene'; import { activateFullSceneTree } from '../utils/test-utils'; @@ -89,11 +90,14 @@ describe('GeneralSettingsEditView', () => { expect(settings.getRefreshPicker()?.state?.intervals).toEqual(['5s']); }); - it('A change to folder updates the dashboard state', () => { - settings.onFolderChange('folder-2', 'folder 2'); + it('A change to folder updates the dashboard state', async () => { + const updateNavModel = jest.spyOn(utils, 'updateNavModel').mockImplementation(jest.fn()); + + await settings.onFolderChange('folder-2', 'folder 2'); expect(dashboard.state.meta.folderUid).toBe('folder-2'); expect(dashboard.state.meta.folderTitle).toBe('folder 2'); + expect(updateNavModel).toHaveBeenCalledWith('folder-2'); }); it('A change to tooltip settings updates the dashboard state', () => { diff --git a/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx b/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx index 8c787cda9cb..669dcc109d8 100644 --- a/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx +++ b/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx @@ -23,6 +23,7 @@ import { DeleteDashboardButton } from 'app/features/dashboard/components/DeleteD import { GenAIDashDescriptionButton } from 'app/features/dashboard/components/GenAI/GenAIDashDescriptionButton'; import { GenAIDashTitleButton } from 'app/features/dashboard/components/GenAI/GenAIDashTitleButton'; +import { updateNavModel } from '../pages/utils'; import { DashboardScene } from '../scene/DashboardScene'; import { NavToolbarActions } from '../scene/NavToolbarActions'; import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; @@ -96,13 +97,17 @@ export class GeneralSettingsEditView this._dashboard.setState({ tags: value }); }; - public onFolderChange = (newUID: string | undefined, newTitle: string | undefined) => { + public onFolderChange = async (newUID: string | undefined, newTitle: string | undefined) => { const newMeta = { ...this._dashboard.state.meta, folderUid: newUID || this._dashboard.state.meta.folderUid, folderTitle: newTitle || this._dashboard.state.meta.folderTitle, }; + if (newMeta.folderUid) { + await updateNavModel(newMeta.folderUid); + } + this._dashboard.setState({ meta: newMeta }); }; From 1ea7dc92508ca51dfb29f54883a2a487f6441395 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 10:00:34 +0000 Subject: [PATCH 05/17] Update dependency @grafana/plugin-e2e to v1.1.1 --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 4c9bc730654..4fafcc676cf 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "@emotion/eslint-plugin": "11.11.0", "@grafana/eslint-config": "7.0.0", "@grafana/eslint-plugin": "link:./packages/grafana-eslint-rules", - "@grafana/plugin-e2e": "1.1.0", + "@grafana/plugin-e2e": "1.1.1", "@grafana/tsconfig": "^1.3.0-rc1", "@manypkg/get-packages": "^2.2.0", "@playwright/test": "1.43.1", diff --git a/yarn.lock b/yarn.lock index 46c60360e89..9e7bb44024f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3981,16 +3981,16 @@ __metadata: languageName: unknown linkType: soft -"@grafana/plugin-e2e@npm:1.1.0": - version: 1.1.0 - resolution: "@grafana/plugin-e2e@npm:1.1.0" +"@grafana/plugin-e2e@npm:1.1.1": + version: 1.1.1 + resolution: "@grafana/plugin-e2e@npm:1.1.1" dependencies: semver: "npm:^7.5.4" uuid: "npm:^9.0.1" yaml: "npm:^2.3.4" peerDependencies: "@playwright/test": ^1.41.2 - checksum: 10/3bfbf97501a1e4ec0e80dca23dfbd4f1c8f93b3546dfde6610db7d7c96455db2a59d3a75c3733c2722f6fc11503293208525b51d25086744e6093dd19591810b + checksum: 10/81d7732fc483bf2b2dd096e3a1efa5b12a32a67a79da9e92d8d502521928b2a75c735666f6b6c0de34a0d530243789ce5e12cac7a4655d425ce2e4f0033bbb11 languageName: node linkType: hard @@ -18624,7 +18624,7 @@ __metadata: "@grafana/lezer-logql": "npm:0.2.3" "@grafana/monaco-logql": "npm:^0.0.7" "@grafana/o11y-ds-frontend": "workspace:*" - "@grafana/plugin-e2e": "npm:1.1.0" + "@grafana/plugin-e2e": "npm:1.1.1" "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" "@grafana/saga-icons": "workspace:*" From c5ca90747d6b09346345058d5e7c670841318a62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Fern=C3=A1ndez?= Date: Fri, 19 Apr 2024 12:34:49 +0200 Subject: [PATCH 06/17] Grafana UI: `TagsInput.story.tsx` - Delete unnecessary `VerticalGroup` (#86582) --- .betterer.results | 3 --- .../src/components/TagsInput/TagsInput.story.tsx | 9 +++------ 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/.betterer.results b/.betterer.results index 54e1a496e23..144d040cfcf 100644 --- a/.betterer.results +++ b/.betterer.results @@ -958,9 +958,6 @@ exports[`better eslint`] = { "packages/grafana-ui/src/components/Tags/Tag.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], - "packages/grafana-ui/src/components/TagsInput/TagsInput.story.tsx:5381": [ - [0, 0, 0, "\'VerticalGroup\' import from \'../Layout/Layout\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "packages/grafana-ui/src/components/Text/Text.story.tsx:5381": [ [0, 0, 0, "\'VerticalGroup\' import from \'../Layout/Layout\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], diff --git a/packages/grafana-ui/src/components/TagsInput/TagsInput.story.tsx b/packages/grafana-ui/src/components/TagsInput/TagsInput.story.tsx index b30cc06ed17..b0d830469bd 100644 --- a/packages/grafana-ui/src/components/TagsInput/TagsInput.story.tsx +++ b/packages/grafana-ui/src/components/TagsInput/TagsInput.story.tsx @@ -2,7 +2,6 @@ import { Meta, StoryFn } from '@storybook/react'; import React, { useState } from 'react'; import { StoryExample } from '../../utils/storybook/StoryExample'; -import { VerticalGroup } from '../Layout/Layout'; import { TagsInput } from './TagsInput'; import mdx from './TagsInput.mdx'; @@ -28,11 +27,9 @@ export const Basic: StoryFn = (props) => { export const WithManyTags = () => { const [tags, setTags] = useState(['dashboard', 'prod', 'server', 'frontend', 'game', 'kubernetes']); return ( - - - - - + + + ); }; From 44e1bce55a27320462c646a1aff577b4abae43a2 Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Fri, 19 Apr 2024 12:48:08 +0200 Subject: [PATCH 07/17] Feature toggles: Remove dashboardEmbed toggle (#86587) --- .../configure-grafana/feature-toggles/index.md | 1 - packages/grafana-data/src/types/featureToggles.gen.ts | 1 - pkg/api/api.go | 4 ---- pkg/middleware/middleware.go | 8 -------- pkg/services/featuremgmt/registry.go | 7 ------- pkg/services/featuremgmt/toggles_gen.csv | 1 - pkg/services/featuremgmt/toggles_gen.go | 4 ---- pkg/services/featuremgmt/toggles_gen.json | 3 ++- 8 files changed, 2 insertions(+), 27 deletions(-) diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 4fd4ca7182f..9e04b40cf6a 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -131,7 +131,6 @@ Experimental features might be changed or removed without prior notice. | `extraThemes` | Enables extra themes | | `lokiPredefinedOperations` | Adds predefined query operations to Loki query editor | | `pluginsFrontendSandbox` | Enables the plugins frontend sandbox | -| `dashboardEmbed` | Allow embedding dashboard for external use in Code editors | | `frontendSandboxMonitorOnly` | Enables monitor only in the plugin frontend sandbox (if enabled) | | `lokiFormatQuery` | Enables the ability to format Loki queries | | `vizAndWidgetSplit` | Split panels between visualizations and widgets | diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 3ac4413419c..63ff19f663e 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -81,7 +81,6 @@ export interface FeatureToggles { extraThemes?: boolean; lokiPredefinedOperations?: boolean; pluginsFrontendSandbox?: boolean; - dashboardEmbed?: boolean; frontendSandboxMonitorOnly?: boolean; sqlDatasourceDatabaseSelection?: boolean; lokiFormatQuery?: boolean; diff --git a/pkg/api/api.go b/pkg/api/api.go index 235af6462cb..337f26819d7 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -151,10 +151,6 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/dashboards/*", reqSignedIn, hs.Index) r.Get("/goto/:uid", reqSignedIn, hs.redirectFromShortURL, hs.Index) - if hs.Features.IsEnabledGlobally(featuremgmt.FlagDashboardEmbed) { - r.Get("/d-embed", reqSignedIn, middleware.AddAllowEmbeddingHeader(), hs.Index) - } - if hs.Features.IsEnabledGlobally(featuremgmt.FlagPublicDashboards) && hs.Cfg.PublicDashboardsEnabled { // list public dashboards r.Get("/public-dashboards/list", reqSignedIn, hs.Index) diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go index 074139c493e..586b885a5d5 100644 --- a/pkg/middleware/middleware.go +++ b/pkg/middleware/middleware.go @@ -66,14 +66,6 @@ func AddDefaultResponseHeaders(cfg *setting.Cfg) web.Handler { } } -func AddAllowEmbeddingHeader() web.Handler { - return func(c *web.Context) { - c.Resp.Before(func(w web.ResponseWriter) { - w.Header().Set("X-Allow-Embedding", "allow") - }) - } -} - // addSecurityHeaders adds HTTP(S) response headers that enable various security protections in the client's browser. func addSecurityHeaders(w web.ResponseWriter, cfg *setting.Cfg) { if cfg.StrictTransportSecurity { diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 7bc464263bd..31ff12fdaa2 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -471,13 +471,6 @@ var ( FrontendOnly: true, Owner: grafanaPluginsPlatformSquad, }, - { - Name: "dashboardEmbed", - Description: "Allow embedding dashboard for external use in Code editors", - FrontendOnly: true, - Stage: FeatureStageExperimental, - Owner: grafanaAsCodeSquad, - }, { Name: "frontendSandboxMonitorOnly", Description: "Enables monitor only in the plugin frontend sandbox (if enabled)", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index e227e488de6..6b6a95141b5 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -62,7 +62,6 @@ enableDatagridEditing,preview,@grafana/dataviz-squad,false,false,true extraThemes,experimental,@grafana/grafana-frontend-platform,false,false,true lokiPredefinedOperations,experimental,@grafana/observability-logs,false,false,true pluginsFrontendSandbox,experimental,@grafana/plugins-platform-backend,false,false,true -dashboardEmbed,experimental,@grafana/grafana-as-code,false,false,true frontendSandboxMonitorOnly,experimental,@grafana/plugins-platform-backend,false,false,true sqlDatasourceDatabaseSelection,preview,@grafana/dataviz-squad,false,false,true lokiFormatQuery,experimental,@grafana/observability-logs,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 34bd5c41dd5..e63bf491bc6 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -259,10 +259,6 @@ const ( // Enables the plugins frontend sandbox FlagPluginsFrontendSandbox = "pluginsFrontendSandbox" - // FlagDashboardEmbed - // Allow embedding dashboard for external use in Code editors - FlagDashboardEmbed = "dashboardEmbed" - // FlagFrontendSandboxMonitorOnly // Enables monitor only in the plugin frontend sandbox (if enabled) FlagFrontendSandboxMonitorOnly = "frontendSandboxMonitorOnly" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index b123aa03f33..24304131755 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -1136,7 +1136,8 @@ "metadata": { "name": "dashboardEmbed", "resourceVersion": "1712639261786", - "creationTimestamp": "2024-04-09T05:07:41Z" + "creationTimestamp": "2024-04-09T05:07:41Z", + "deletionTimestamp": "2024-04-19T10:27:36Z" }, "spec": { "description": "Allow embedding dashboard for external use in Code editors", From 5f7612834e3594fec3de85a55be330ad77e41c2b Mon Sep 17 00:00:00 2001 From: Steve Simpson Date: Fri, 19 Apr 2024 12:52:01 +0200 Subject: [PATCH 08/17] Alerting: Refactoring in api_prometheus.go to allow code reuse. (#86575) Preparing these functions to be used by some other part of the codebase, which does not have a `contextmodel.ReqContext`, only the normal request structure (`url.Values`, etc). This is slightly messy because of how Grafana allows url parameters to be in the URL or in the request body, so we need to make sure to invoke the form parsing logic in `ReqContext`. --- pkg/services/ngalert/api/api_prometheus.go | 40 +++++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/pkg/services/ngalert/api/api_prometheus.go b/pkg/services/ngalert/api/api_prometheus.go index 5c1211f659d..a3161e9eb50 100644 --- a/pkg/services/ngalert/api/api_prometheus.go +++ b/pkg/services/ngalert/api/api_prometheus.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "net/url" "sort" "strconv" "strings" @@ -33,7 +34,33 @@ type PrometheusSrv struct { const queryIncludeInternalLabels = "includeInternalLabels" +func getBoolWithDefault(vals url.Values, field string, d bool) bool { + f := vals.Get(field) + if f == "" { + return d + } + + v, _ := strconv.ParseBool(f) + return v +} + +func getInt64WithDefault(vals url.Values, field string, d int64) int64 { + f := vals.Get(field) + if f == "" { + return d + } + + v, err := strconv.ParseInt(f, 10, 64) + if err != nil { + return d + } + return v +} + func (srv PrometheusSrv) RouteGetAlertStatuses(c *contextmodel.ReqContext) response.Response { + // As we are using req.Form directly, this triggers a call to ParseForm() if needed. + c.Query("") + alertResponse := apimodels.AlertResponse{ DiscoveryBase: apimodels.DiscoveryBase{ Status: "success", @@ -44,7 +71,7 @@ func (srv PrometheusSrv) RouteGetAlertStatuses(c *contextmodel.ReqContext) respo } var labelOptions []ngmodels.LabelOption - if !c.QueryBoolWithDefault(queryIncludeInternalLabels, false) { + if !getBoolWithDefault(c.Req.Form, queryIncludeInternalLabels, false) { labelOptions = append(labelOptions, ngmodels.WithoutInternalLabels()) } @@ -145,6 +172,9 @@ func getStatesFromRequest(r *http.Request) ([]eval.State, error) { } func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) response.Response { + // As we are using req.Form directly, this triggers a call to ParseForm() if needed. + c.Query("") + dashboardUID := c.Query("dashboard_uid") panelID, err := getPanelIDFromRequest(c.Req) if err != nil { @@ -154,9 +184,9 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon return ErrResp(http.StatusBadRequest, errors.New("panel_id must be set with dashboard_uid"), "") } - limitGroups := c.QueryInt64WithDefault("limit", -1) - limitRulesPerGroup := c.QueryInt64WithDefault("limit_rules", -1) - limitAlertsPerRule := c.QueryInt64WithDefault("limit_alerts", -1) + limitGroups := getInt64WithDefault(c.Req.Form, "limit", -1) + limitRulesPerGroup := getInt64WithDefault(c.Req.Form, "limit_rules", -1) + limitAlertsPerRule := getInt64WithDefault(c.Req.Form, "limit_alerts", -1) matchers, err := getMatchersFromRequest(c.Req) if err != nil { return ErrResp(http.StatusBadRequest, err, "") @@ -180,7 +210,7 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon } var labelOptions []ngmodels.LabelOption - if !c.QueryBoolWithDefault(queryIncludeInternalLabels, false) { + if !getBoolWithDefault(c.Req.Form, queryIncludeInternalLabels, false) { labelOptions = append(labelOptions, ngmodels.WithoutInternalLabels()) } From 21588ce7e2e89d8ecdc142cbb8b584d8726c2908 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Fri, 19 Apr 2024 11:52:16 +0100 Subject: [PATCH 09/17] EmptyState: Set a max width on the empty state component (#86569) set a max width on the empty state component --- .../src/components/EmptyState/EmptyState.tsx | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/packages/grafana-ui/src/components/EmptyState/EmptyState.tsx b/packages/grafana-ui/src/components/EmptyState/EmptyState.tsx index 548a1a66477..28d7f67724e 100644 --- a/packages/grafana-ui/src/components/EmptyState/EmptyState.tsx +++ b/packages/grafana-ui/src/components/EmptyState/EmptyState.tsx @@ -1,6 +1,10 @@ +import { css } from '@emotion/css'; import React, { ReactNode } from 'react'; import SVG from 'react-inlinesvg'; +import { GrafanaTheme2 } from '@grafana/data'; + +import { useStyles2 } from '../../themes'; import { Box } from '../Layout/Box/Box'; import { Stack } from '../Layout/Stack/Stack'; import { Text } from '../Text/Text'; @@ -37,22 +41,25 @@ export const EmptyState = ({ hideImage = false, variant, }: React.PropsWithChildren) => { + const styles = useStyles2(getStyles); const imageToShow = image ?? getDefaultImageForVariant(variant); return ( - - {!hideImage && imageToShow} - - - {message} - - {children && ( - - {children} + +
+ {!hideImage && imageToShow} + + + {message} - )} - - {button} + {children && ( + + {children} + + )} + + {button} +
); }; @@ -73,3 +80,13 @@ function getDefaultImageForVariant(variant: Props['variant']) { } } } + +const getStyles = (theme: GrafanaTheme2) => ({ + container: css({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: theme.spacing(4), + maxWidth: '600px', + }), +}); From 63427ccd9833fee59528a99704a4d1678d5f3723 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Fri, 19 Apr 2024 11:52:27 +0100 Subject: [PATCH 10/17] CommandPalette: Fix keyboard shortcut alignment (#86540) * use full typography properties * use Text component --- .../AppChrome/TopBar/TopSearchBarCommandPaletteTrigger.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/public/app/core/components/AppChrome/TopBar/TopSearchBarCommandPaletteTrigger.tsx b/public/app/core/components/AppChrome/TopBar/TopSearchBarCommandPaletteTrigger.tsx index 92ffcfc22d9..40d4e61e33d 100644 --- a/public/app/core/components/AppChrome/TopBar/TopSearchBarCommandPaletteTrigger.tsx +++ b/public/app/core/components/AppChrome/TopBar/TopSearchBarCommandPaletteTrigger.tsx @@ -3,7 +3,7 @@ import { useKBar, VisualState } from 'kbar'; import React, { useMemo, useState } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; -import { getInputStyles, Icon, ToolbarButton, useStyles2, useTheme2 } from '@grafana/ui'; +import { getInputStyles, Icon, Text, ToolbarButton, useStyles2, useTheme2 } from '@grafana/ui'; import { focusCss } from '@grafana/ui/src/themes/mixins'; import { useMediaQueryChange } from 'app/core/hooks/useMediaQueryChange'; import { t } from 'app/core/internationalization'; @@ -70,7 +70,7 @@ function PretendTextInput({ onClick }: PretendTextInputProps) {
- {modKey}+k + {modKey}+k
@@ -91,9 +91,6 @@ const getStyles = (theme: GrafanaTheme2) => { gap: theme.spacing(0.5), }, ]), - shortcut: css({ - fontSize: theme.typography.bodySmall.fontSize, - }), fakeInput: css([ baseStyles.input, { From 7404a631f6dcf9978ccc66cb96ce17a2a541fc7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Fern=C3=A1ndez?= Date: Fri, 19 Apr 2024 12:54:20 +0200 Subject: [PATCH 11/17] GrafanaUI: `PageToolbar.story.tsx` - Replace `VerticalGroup` with `Stack` (#86581) --- .betterer.results | 3 --- .../src/components/PageLayout/PageToolbar.story.tsx | 6 +++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.betterer.results b/.betterer.results index 144d040cfcf..c5b50784207 100644 --- a/.betterer.results +++ b/.betterer.results @@ -838,9 +838,6 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "2"], [0, 0, 0, "Unexpected any. Specify a different type.", "3"] ], - "packages/grafana-ui/src/components/PageLayout/PageToolbar.story.tsx:5381": [ - [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "packages/grafana-ui/src/components/PanelChrome/PanelContext.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] diff --git a/packages/grafana-ui/src/components/PageLayout/PageToolbar.story.tsx b/packages/grafana-ui/src/components/PageLayout/PageToolbar.story.tsx index 0c736075d53..6e9638efdf5 100644 --- a/packages/grafana-ui/src/components/PageLayout/PageToolbar.story.tsx +++ b/packages/grafana-ui/src/components/PageLayout/PageToolbar.story.tsx @@ -2,7 +2,7 @@ import { action } from '@storybook/addon-actions'; import { Meta } from '@storybook/react'; import React from 'react'; -import { ToolbarButton, VerticalGroup } from '@grafana/ui'; +import { ToolbarButton, Stack } from '@grafana/ui'; import { StoryExample } from '../../utils/storybook/StoryExample'; import { IconButton } from '../IconButton/IconButton'; @@ -17,7 +17,7 @@ const meta: Meta = { export const Examples = () => { return ( - + @@ -50,7 +50,7 @@ export const Examples = () => { Apply - +
); }; From 9878dfb7d9f632fd9cd16f352dbeba938d1f8ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Fern=C3=A1ndez?= Date: Fri, 19 Apr 2024 12:55:04 +0200 Subject: [PATCH 12/17] Grafana UI: `EmotionPerfTest` - Replace `VerticalGroup` with `Stack` (#86588) --- .betterer.results | 3 --- .../src/components/ThemeDemos/EmotionPerfTest.tsx | 6 +++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.betterer.results b/.betterer.results index c5b50784207..cc744e869a2 100644 --- a/.betterer.results +++ b/.betterer.results @@ -958,9 +958,6 @@ exports[`better eslint`] = { "packages/grafana-ui/src/components/Text/Text.story.tsx:5381": [ [0, 0, 0, "\'VerticalGroup\' import from \'../Layout/Layout\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], - "packages/grafana-ui/src/components/ThemeDemos/EmotionPerfTest.tsx:5381": [ - [0, 0, 0, "\'VerticalGroup\' import from \'../Layout/Layout\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx:5381": [ [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"] ], diff --git a/packages/grafana-ui/src/components/ThemeDemos/EmotionPerfTest.tsx b/packages/grafana-ui/src/components/ThemeDemos/EmotionPerfTest.tsx index 3399fff3fc0..df6a5b01ad9 100644 --- a/packages/grafana-ui/src/components/ThemeDemos/EmotionPerfTest.tsx +++ b/packages/grafana-ui/src/components/ThemeDemos/EmotionPerfTest.tsx @@ -9,13 +9,13 @@ import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2, useTheme2 } from '../../themes'; import { Button } from '../Button'; -import { VerticalGroup } from '../Layout/Layout'; +import { Stack } from '../Layout/Stack/Stack'; export function EmotionPerfTest() { console.log('process.env.NODE_ENV', process.env.NODE_ENV); return ( - +
Emotion performance tests
@@ -24,7 +24,7 @@ export function EmotionPerfTest() { -
+ ); } From aa326423ed61f69212a71c384bda89c0ecddbbd5 Mon Sep 17 00:00:00 2001 From: Joao Silva <100691367+JoaoSilvaGrafana@users.noreply.github.com> Date: Fri, 19 Apr 2024 11:58:20 +0100 Subject: [PATCH 13/17] ColorPicker: Improvements to story organization (#86539) --- .betterer.results | 3 -- .../components/ColorPicker/ColorPicker.mdx | 17 +++++++- .../ColorPicker/ColorPicker.story.tsx | 33 +++++++++----- .../ColorPicker/ColorPickerPopover.story.tsx | 13 ++++-- .../ColorPicker/NamedColorsPalette.story.tsx | 36 ---------------- .../components/ColorPicker/Palettes.story.tsx | 43 +++++++++++++++++++ .../ColorPicker/SeriesColorPickerPopover.tsx | 34 +++------------ .../ColorPicker/SpectrumPalette.story.tsx | 37 ---------------- 8 files changed, 95 insertions(+), 121 deletions(-) delete mode 100644 packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx create mode 100644 packages/grafana-ui/src/components/ColorPicker/Palettes.story.tsx delete mode 100644 packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.story.tsx diff --git a/.betterer.results b/.betterer.results index cc744e869a2..0ba1e379ac3 100644 --- a/.betterer.results +++ b/.betterer.results @@ -6371,9 +6371,6 @@ exports[`no undocumented stories`] = { "packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.story.tsx:5381": [ [0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"] ], - "packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx:5381": [ - [0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"] - ], "packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.story.tsx:5381": [ [0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"] ], diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.mdx b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.mdx index 743d87f9042..2f7421f0876 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.mdx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.mdx @@ -1,5 +1,6 @@ import { Meta, ArgTypes } from '@storybook/blocks'; -import { ColorPicker } from './ColorPicker'; +import { ColorPicker, SeriesColorPicker } from './ColorPicker'; +import { ColorPickerInput } from './ColorPickerInput'; @@ -9,7 +10,7 @@ The `ColorPicker` component group consists of several building blocks that are c The `Popover` is a tabbed view where you can switch between `Palettes`. The `NamedColorsPalette` shows an arrangement of preset colors, while the `SpectrumPalette` is an unlimited HSB color picker. The preset colors are optimized to work well with both light and dark theme. `Popover` is triggered, for example, by the series legend of graphs, or by `Pickers`. -The `Pickers` are single circular color fields that show the currently picked color. On click, they open the `Popover`. +The `Pickers` are by default single circular color fields that show the currently picked color. On click, they open the `Popover`. ## ColorPickerInput @@ -17,4 +18,16 @@ Color picker component, modified to be used inside forms. Supports all usual inp The format in which the color is returned to the `onChange` callback can be customised via `returnColorAs` prop. +## Props + +### ColorPicker + + +### SeriesColorPicker + + + +### ColorPickerInput + + diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx index 81579ec60c4..ec2c5e8aa8f 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.story.tsx @@ -3,18 +3,13 @@ import { useArgs } from '@storybook/client-api'; import { Meta, StoryFn } from '@storybook/react'; import React from 'react'; -import { SeriesColorPicker, ColorPicker, clearButtonStyles, useStyles2 } from '@grafana/ui'; +import { SeriesColorPicker, ColorPicker, clearButtonStyles, useStyles2, ColorPickerInput } from '@grafana/ui'; import mdx from './ColorPicker.mdx'; -import { ColorPickerInput } from './ColorPickerInput'; const meta: Meta = { title: 'Pickers and Editors/ColorPicker', component: ColorPicker, - // SB7 has broken subcomponent types due to dropping support for the feature - // https://github.com/storybookjs/storybook/issues/20782 - // @ts-ignore - subcomponents: { SeriesColorPicker, ColorPickerInput }, parameters: { docs: { page: mdx, @@ -47,15 +42,31 @@ export const Basic: StoryFn = ({ color, enableNamedColors }) }; export const SeriesPicker: StoryFn = ({ color, enableNamedColors }) => { + const [, updateArgs] = useArgs(); + return ( +
+ {}} + color={color} + onChange={(color) => { + action('Color changed')(color); + updateArgs({ color }); + }} + /> +
+ ); +}; + +export const CustomTrigger: StoryFn = ({ color, enableNamedColors }) => { const [, updateArgs] = useArgs(); const clearButton = useStyles2(clearButtonStyles); return ( - {}} color={color} - onChange={(color) => { + onChange={(color: string) => { action('Color changed')(color); updateArgs({ color }); }} @@ -72,7 +83,7 @@ export const SeriesPicker: StoryFn = ({ color, enableN Open color picker )} - + ); }; diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx index 70aae18a5b1..2a2b6fbba99 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.story.tsx @@ -3,16 +3,18 @@ import React from 'react'; import { useTheme2 } from '../../themes'; +import mdx from './ColorPicker.mdx'; import { ColorPickerPopover } from './ColorPickerPopover'; import { SeriesColorPickerPopover } from './SeriesColorPickerPopover'; const meta: Meta = { title: 'Pickers and Editors/ColorPicker/Popovers', component: ColorPickerPopover, - // SB7 has broken subcomponent types due to dropping support for the feature - // https://github.com/storybookjs/storybook/issues/20782 - // @ts-ignore - subcomponents: { SeriesColorPickerPopover }, + parameters: { + docs: { + page: mdx, + }, + }, }; export const Basic = () => { @@ -30,11 +32,14 @@ export const Basic = () => { export const SeriesColorPickerPopoverExample = () => { const theme = useTheme2(); + const [yAxis, setYAxis] = React.useState(0); return (
(yAxis ? setYAxis(0) : setYAxis(2))} color="#BC67E6" onChange={(color: string) => { console.log(color); diff --git a/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx deleted file mode 100644 index 8f76f18b107..00000000000 --- a/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { Meta, Story } from '@storybook/react'; -import React, { useState } from 'react'; - -import mdx from './ColorPicker.mdx'; -import { NamedColorsPalette, NamedColorsPaletteProps } from './NamedColorsPalette'; - -const meta: Meta = { - title: 'Pickers and Editors/ColorPicker/Palettes/NamedColorsPalette', - component: NamedColorsPalette, - parameters: { - docs: { - page: mdx, - }, - controls: { - exclude: ['theme', 'color'], - }, - }, - argTypes: { - selectedColor: { control: { type: 'select', options: ['green', 'red', 'light-blue', 'yellow'] } }, - }, -}; - -interface StoryProps extends Partial { - selectedColor: string; -} - -export const NamedColors: Story = ({ selectedColor }) => { - const [color, setColor] = useState('green'); - return ; -}; - -NamedColors.args = { - color: 'green', -}; - -export default meta; diff --git a/packages/grafana-ui/src/components/ColorPicker/Palettes.story.tsx b/packages/grafana-ui/src/components/ColorPicker/Palettes.story.tsx new file mode 100644 index 00000000000..0308d31783a --- /dev/null +++ b/packages/grafana-ui/src/components/ColorPicker/Palettes.story.tsx @@ -0,0 +1,43 @@ +import { action } from '@storybook/addon-actions'; +import { useArgs } from '@storybook/client-api'; +import { Meta, StoryFn } from '@storybook/react'; +import React, { useState } from 'react'; + +import mdx from './ColorPicker.mdx'; +import { NamedColorsPalette } from './NamedColorsPalette'; +import SpectrumPalette from './SpectrumPalette'; + +const meta: Meta = { + title: 'Pickers and Editors/ColorPicker/Palettes', + parameters: { + docs: { + page: mdx, + }, + controls: { + exclude: ['theme', 'color'], + }, + }, + args: { + color: 'green', + }, +}; + +export const NamedColors: StoryFn = ({ color }) => { + const [colorVal, setColor] = useState(color); + return ; +}; + +export const Spectrum: StoryFn = ({ color }) => { + const [, updateArgs] = useArgs(); + return ( + { + action('Color changed')(color); + updateArgs({ color }); + }} + /> + ); +}; + +export default meta; diff --git a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx index d1ca21ad805..90f5e80f605 100644 --- a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx @@ -1,8 +1,8 @@ -import { css } from '@emotion/css'; import React from 'react'; -import { withTheme2, useStyles2 } from '../../themes'; -import { Switch } from '../Forms/Legacy/Switch/Switch'; +import { withTheme2 } from '../../themes'; +import { InlineField } from '../Forms/InlineField'; +import { InlineSwitch } from '../Switch/Switch'; import { PopoverContentProps } from '../Tooltip'; import { ColorPickerPopover, ColorPickerProps } from './ColorPickerPopover'; @@ -13,7 +13,6 @@ export interface SeriesColorPickerPopoverProps extends ColorPickerProps, Popover } export const SeriesColorPickerPopover = (props: SeriesColorPickerPopoverProps) => { - const styles = useStyles2(getStyles); const { yaxis, onToggleAxis, color, ...colorPickerProps } = props; const customPickers = onToggleAxis @@ -22,18 +21,9 @@ export const SeriesColorPickerPopover = (props: SeriesColorPickerPopoverProps) = name: 'Y-Axis', tabComponent() { return ( - { - if (onToggleAxis) { - onToggleAxis(); - } - }} - /> + + + ); }, }, @@ -44,15 +34,3 @@ export const SeriesColorPickerPopover = (props: SeriesColorPickerPopoverProps) = // This component is to enable SeriesColorPickerPopover usage via series-color-picker-popover directive export const SeriesColorPickerPopoverWithTheme = withTheme2(SeriesColorPickerPopover); - -const getStyles = () => { - return { - colorPickerAxisSwitch: css({ - width: '100%', - }), - colorPickerAxisSwitchLabel: css({ - display: 'flex', - flexGrow: 1, - }), - }; -}; diff --git a/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.story.tsx b/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.story.tsx deleted file mode 100644 index ab03ae9076f..00000000000 --- a/packages/grafana-ui/src/components/ColorPicker/SpectrumPalette.story.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { action } from '@storybook/addon-actions'; -import { useArgs } from '@storybook/client-api'; -import { Meta, StoryFn } from '@storybook/react'; - -import { renderComponentWithTheme } from '../../utils/storybook/withTheme'; - -import mdx from './ColorPicker.mdx'; -import SpectrumPalette from './SpectrumPalette'; - -const meta: Meta = { - title: 'Pickers and Editors/ColorPicker/Palettes/SpectrumPalette', - component: SpectrumPalette, - parameters: { - docs: { - page: mdx, - }, - controls: { - exclude: ['onChange'], - }, - }, - args: { - color: 'red', - }, -}; - -export const Simple: StoryFn = ({ color }) => { - const [, updateArgs] = useArgs(); - return renderComponentWithTheme(SpectrumPalette, { - color, - onChange: (color: string) => { - action('Color changed')(color); - updateArgs({ color }); - }, - }); -}; - -export default meta; From 65afe90124cd57deae18f6955b5f286a4dd438e9 Mon Sep 17 00:00:00 2001 From: Ivan Ortega Alba Date: Fri, 19 Apr 2024 13:04:01 +0200 Subject: [PATCH 14/17] IntervalVariableEditor: Do not add current value as interval prop (#86446) --- .../editors/IntervalVariableEditor.test.tsx | 3 +++ .../variables/editors/IntervalVariableEditor.tsx | 13 ++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/public/app/features/dashboard-scene/settings/variables/editors/IntervalVariableEditor.test.tsx b/public/app/features/dashboard-scene/settings/variables/editors/IntervalVariableEditor.test.tsx index 9e2e16c44ec..5e1ddd110e2 100644 --- a/public/app/features/dashboard-scene/settings/variables/editors/IntervalVariableEditor.test.tsx +++ b/public/app/features/dashboard-scene/settings/variables/editors/IntervalVariableEditor.test.tsx @@ -46,6 +46,7 @@ describe('IntervalVariableEditor', () => { name: 'test', type: 'interval', intervals: ['1m', '10m', '1h', '6h', '1d', '7d'], + value: '10m', }); const onRunQuery = jest.fn(); @@ -61,6 +62,8 @@ describe('IntervalVariableEditor', () => { expect(intervalsInput).toBeInTheDocument(); expect(intervalsInput).toHaveValue('7d,30d, 1y, 5y, 10y'); + // If the value is not in the list, it should be set to the first value + expect(variable.state.value).toBe('7d'); expect(onRunQuery).toHaveBeenCalledTimes(1); }); diff --git a/public/app/features/dashboard-scene/settings/variables/editors/IntervalVariableEditor.tsx b/public/app/features/dashboard-scene/settings/variables/editors/IntervalVariableEditor.tsx index 6ff69c2b0c5..3ffc414d7f4 100644 --- a/public/app/features/dashboard-scene/settings/variables/editors/IntervalVariableEditor.tsx +++ b/public/app/features/dashboard-scene/settings/variables/editors/IntervalVariableEditor.tsx @@ -15,14 +15,21 @@ interface IntervalVariableEditorProps { } export function IntervalVariableEditor({ variable, onRunQuery }: IntervalVariableEditorProps) { - const { intervals, autoStepCount, autoEnabled, autoMinInterval } = variable.useState(); + const { intervals, autoStepCount, autoEnabled, autoMinInterval, value } = variable.useState(); //transform intervals array into string const intervalsCombined = getIntervalsQueryFromNewIntervalModel(intervals); const onIntervalsChange = (event: FormEvent) => { - const intervalsArray = getIntervalsFromQueryString(event.currentTarget.value); - variable.setState({ intervals: intervalsArray }); + const newIntervals = getIntervalsFromQueryString(event.currentTarget.value); + // if the current value is not in the new intervals, set the value to the first interval + const newValue = newIntervals.includes(value) ? value : newIntervals[0]; + + variable.setState({ + intervals: newIntervals, + value: newValue, + }); + onRunQuery(); }; From a2ce8fefed7fae615b32d3fcf5b78a5e97ea2173 Mon Sep 17 00:00:00 2001 From: Santiago Date: Fri, 19 Apr 2024 13:04:18 +0200 Subject: [PATCH 15/17] Alerting: Use a struct when sending a Grafana AM configuration to the remote Alertmanager (#86451) * Alerting: Use a struct when sending a Grafana AM configuration to the remote Alertmanager * remove '-distroless' from mimir image name --- .drone.yml | 14 +++++------ .../blocks/mimir_backend/docker-compose.yaml | 2 +- pkg/services/ngalert/remote/alertmanager.go | 25 +++++++++++-------- .../ngalert/remote/alertmanager_test.go | 18 ++++++++++--- .../client/alertmanager_configuration.go | 12 +++++---- pkg/services/ngalert/remote/client/mimir.go | 3 ++- scripts/drone/utils/images.star | 2 +- 7 files changed, 47 insertions(+), 29 deletions(-) diff --git a/.drone.yml b/.drone.yml index 9f6ba848ad2..b6399fe0c64 100644 --- a/.drone.yml +++ b/.drone.yml @@ -879,7 +879,7 @@ services: - commands: - /bin/mimir -target=backend -alertmanager.grafana-alertmanager-compatibility-enabled environment: {} - image: us.gcr.io/kubernetes-dev/mimir:santihernandezc-remove_id_from_grafana_config-d3826b4f8-WIP + image: us.gcr.io/kubernetes-dev/mimir:santihernandezc-validate_grafana_am_config-1e903e462-WIP name: mimir_backend - environment: {} image: redis:6.2.11-alpine @@ -1327,7 +1327,7 @@ services: - commands: - /bin/mimir -target=backend -alertmanager.grafana-alertmanager-compatibility-enabled environment: {} - image: us.gcr.io/kubernetes-dev/mimir:santihernandezc-remove_id_from_grafana_config-d3826b4f8-WIP + image: us.gcr.io/kubernetes-dev/mimir:santihernandezc-validate_grafana_am_config-1e903e462-WIP name: mimir_backend - environment: {} image: redis:6.2.11-alpine @@ -2329,7 +2329,7 @@ services: - commands: - /bin/mimir -target=backend -alertmanager.grafana-alertmanager-compatibility-enabled environment: {} - image: us.gcr.io/kubernetes-dev/mimir:santihernandezc-remove_id_from_grafana_config-d3826b4f8-WIP + image: us.gcr.io/kubernetes-dev/mimir:santihernandezc-validate_grafana_am_config-1e903e462-WIP name: mimir_backend - environment: {} image: redis:6.2.11-alpine @@ -4127,7 +4127,7 @@ services: - commands: - /bin/mimir -target=backend -alertmanager.grafana-alertmanager-compatibility-enabled environment: {} - image: us.gcr.io/kubernetes-dev/mimir:santihernandezc-remove_id_from_grafana_config-d3826b4f8-WIP + image: us.gcr.io/kubernetes-dev/mimir:santihernandezc-validate_grafana_am_config-1e903e462-WIP name: mimir_backend - environment: {} image: redis:6.2.11-alpine @@ -4646,7 +4646,7 @@ steps: - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM plugins/slack - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM python:3.8 - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM postgres:12.3-alpine - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM us.gcr.io/kubernetes-dev/mimir:santihernandezc-remove_id_from_grafana_config-d3826b4f8-WIP + - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM us.gcr.io/kubernetes-dev/mimir:santihernandezc-validate_grafana_am_config-1e903e462-WIP - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM mysql:5.7.39 - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM mysql:8.0.32 - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM redis:6.2.11-alpine @@ -4681,7 +4681,7 @@ steps: - trivy --exit-code 1 --severity HIGH,CRITICAL plugins/slack - trivy --exit-code 1 --severity HIGH,CRITICAL python:3.8 - trivy --exit-code 1 --severity HIGH,CRITICAL postgres:12.3-alpine - - trivy --exit-code 1 --severity HIGH,CRITICAL us.gcr.io/kubernetes-dev/mimir:santihernandezc-remove_id_from_grafana_config-d3826b4f8-WIP + - trivy --exit-code 1 --severity HIGH,CRITICAL us.gcr.io/kubernetes-dev/mimir:santihernandezc-validate_grafana_am_config-1e903e462-WIP - trivy --exit-code 1 --severity HIGH,CRITICAL mysql:5.7.39 - trivy --exit-code 1 --severity HIGH,CRITICAL mysql:8.0.32 - trivy --exit-code 1 --severity HIGH,CRITICAL redis:6.2.11-alpine @@ -4925,6 +4925,6 @@ kind: secret name: gcr_credentials --- kind: signature -hmac: 958ed40ca0620498c01fa867b05a1d6c3bcd908067fbb34be691923e95cfc77b +hmac: e67367689de11270bb3139f25a7cabf54e2b980460fd55ad410c590722d122b8 ... diff --git a/devenv/docker/blocks/mimir_backend/docker-compose.yaml b/devenv/docker/blocks/mimir_backend/docker-compose.yaml index 2a9c2dbd98e..76a19755d9f 100644 --- a/devenv/docker/blocks/mimir_backend/docker-compose.yaml +++ b/devenv/docker/blocks/mimir_backend/docker-compose.yaml @@ -1,5 +1,5 @@ mimir_backend: - image: us.gcr.io/kubernetes-dev/mimir:santihernandezc-remove_id_from_grafana_config-d3826b4f8-WIP + image: us.gcr.io/kubernetes-dev/mimir:santihernandezc-validate_grafana_am_config-1e903e462-WIP container_name: mimir_backend command: - -target=backend diff --git a/pkg/services/ngalert/remote/alertmanager.go b/pkg/services/ngalert/remote/alertmanager.go index 66a78e6747b..322e9d4e4ce 100644 --- a/pkg/services/ngalert/remote/alertmanager.go +++ b/pkg/services/ngalert/remote/alertmanager.go @@ -166,7 +166,6 @@ func (am *Alertmanager) ApplyConfig(ctx context.Context, config *models.AlertCon am.log.Error("Unable to upload the state to the remote Alertmanager", "err", err) } am.log.Debug("Completed state upload to remote Alertmanager", "url", am.url) - return nil } @@ -202,20 +201,16 @@ func (am *Alertmanager) CompareAndSendConfiguration(ctx context.Context, config if err != nil { return err } - rawDecrypted, err := json.Marshal(decrypted) - if err != nil { - return err - } // Send the configuration only if we need to. - if !am.shouldSendConfig(ctx, rawDecrypted) { + if !am.shouldSendConfig(ctx, &decrypted) { return nil } am.metrics.ConfigSyncsTotal.Inc() if err := am.mimirClient.CreateGrafanaAlertmanagerConfig( ctx, - string(rawDecrypted), + &decrypted, config.ConfigurationHash, config.CreatedAt, config.Default, @@ -447,15 +442,25 @@ func (am *Alertmanager) getFullState(ctx context.Context) (string, error) { // shouldSendConfig compares the remote Alertmanager configuration with our local one. // It returns true if the configurations are different. -func (am *Alertmanager) shouldSendConfig(ctx context.Context, rawConfig []byte) bool { +func (am *Alertmanager) shouldSendConfig(ctx context.Context, config *apimodels.PostableUserConfig) bool { rc, err := am.mimirClient.GetGrafanaAlertmanagerConfig(ctx) if err != nil { // Log the error and return true so we try to upload our config anyway. - am.log.Error("Unable to get the remote Alertmanager Configuration for comparison", "err", err) + am.log.Error("Unable to get the remote Alertmanager configuration for comparison", "err", err) return true } - return md5.Sum([]byte(rc.GrafanaAlertmanagerConfig)) != md5.Sum(rawConfig) + rawRemote, err := json.Marshal(rc.GrafanaAlertmanagerConfig) + if err != nil { + am.log.Error("Unable to marshal the remote Alertmanager configuration for comparison", "err", err) + return true + } + rawInternal, err := json.Marshal(config) + if err != nil { + am.log.Error("Unable to marshal the internal Alertmanager configuration for comparison", "err", err) + return true + } + return md5.Sum(rawRemote) != md5.Sum(rawInternal) } // shouldSendState compares the remote Alertmanager state with our local one. diff --git a/pkg/services/ngalert/remote/alertmanager_test.go b/pkg/services/ngalert/remote/alertmanager_test.go index d11d8bf903b..947c270ac2d 100644 --- a/pkg/services/ngalert/remote/alertmanager_test.go +++ b/pkg/services/ngalert/remote/alertmanager_test.go @@ -118,7 +118,9 @@ func TestApplyConfig(t *testing.T) { if r.Method == http.MethodPost && strings.Contains(r.URL.Path, "/config") { var c client.UserGrafanaConfig require.NoError(t, json.NewDecoder(r.Body).Decode(&c)) - configSent = c.GrafanaAlertmanagerConfig + amCfg, err := json.Marshal(c.GrafanaAlertmanagerConfig) + require.NoError(t, err) + configSent = string(amCfg) } w.WriteHeader(http.StatusOK) @@ -179,6 +181,8 @@ func TestApplyConfig(t *testing.T) { } func TestCompareAndSendConfiguration(t *testing.T) { + cfgWithSecret, err := notifier.Load([]byte(testGrafanaConfigWithSecret)) + require.NoError(t, err) testValue := []byte("test") testErr := errors.New("test error") decryptFn := func(_ context.Context, payload []byte) ([]byte, error) { @@ -243,7 +247,7 @@ func TestCompareAndSendConfiguration(t *testing.T) { "no error", strings.Replace(testGrafanaConfigWithSecret, `"password":"test"`, fmt.Sprintf("%q:%q", "password", base64.StdEncoding.EncodeToString(testValue)), 1), &client.UserGrafanaConfig{ - GrafanaAlertmanagerConfig: testGrafanaConfigWithSecret, + GrafanaAlertmanagerConfig: cfgWithSecret, }, "", }, @@ -335,7 +339,10 @@ func TestIntegrationRemoteAlertmanagerApplyConfigOnlyUploadsOnce(t *testing.T) { // Next, we need to verify that Mimir received both the configuration and state. config, err := am.mimirClient.GetGrafanaAlertmanagerConfig(ctx) require.NoError(t, err) - require.Equal(t, testGrafanaConfig, config.GrafanaAlertmanagerConfig) + + rawCfg, err := json.Marshal(config.GrafanaAlertmanagerConfig) + require.NoError(t, err) + require.JSONEq(t, testGrafanaConfig, string(rawCfg)) require.Equal(t, fakeConfigHash, config.Hash) require.Equal(t, fakeConfigCreatedAt, config.CreatedAt) require.Equal(t, true, config.Default) @@ -358,7 +365,10 @@ func TestIntegrationRemoteAlertmanagerApplyConfigOnlyUploadsOnce(t *testing.T) { // Next, we need to verify that the config that was uploaded remains the same. config, err := am.mimirClient.GetGrafanaAlertmanagerConfig(ctx) require.NoError(t, err) - require.Equal(t, testGrafanaConfig, config.GrafanaAlertmanagerConfig) + + rawCfg, err := json.Marshal(config.GrafanaAlertmanagerConfig) + require.NoError(t, err) + require.JSONEq(t, testGrafanaConfig, string(rawCfg)) require.Equal(t, fakeConfigHash, config.Hash) require.Equal(t, fakeConfigCreatedAt, config.CreatedAt) require.Equal(t, true, config.Default) diff --git a/pkg/services/ngalert/remote/client/alertmanager_configuration.go b/pkg/services/ngalert/remote/client/alertmanager_configuration.go index 6ddf6dfdaae..e0f19a8efe3 100644 --- a/pkg/services/ngalert/remote/client/alertmanager_configuration.go +++ b/pkg/services/ngalert/remote/client/alertmanager_configuration.go @@ -6,6 +6,8 @@ import ( "encoding/json" "fmt" "net/http" + + apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" ) const ( @@ -13,10 +15,10 @@ const ( ) type UserGrafanaConfig struct { - GrafanaAlertmanagerConfig string `json:"configuration"` - Hash string `json:"configuration_hash"` - CreatedAt int64 `json:"created"` - Default bool `json:"default"` + GrafanaAlertmanagerConfig *apimodels.PostableUserConfig `json:"configuration"` + Hash string `json:"configuration_hash"` + CreatedAt int64 `json:"created"` + Default bool `json:"default"` } func (mc *Mimir) GetGrafanaAlertmanagerConfig(ctx context.Context) (*UserGrafanaConfig, error) { @@ -38,7 +40,7 @@ func (mc *Mimir) GetGrafanaAlertmanagerConfig(ctx context.Context) (*UserGrafana return gc, nil } -func (mc *Mimir) CreateGrafanaAlertmanagerConfig(ctx context.Context, cfg, hash string, createdAt int64, isDefault bool) error { +func (mc *Mimir) CreateGrafanaAlertmanagerConfig(ctx context.Context, cfg *apimodels.PostableUserConfig, hash string, createdAt int64, isDefault bool) error { payload, err := json.Marshal(&UserGrafanaConfig{ GrafanaAlertmanagerConfig: cfg, Hash: hash, diff --git a/pkg/services/ngalert/remote/client/mimir.go b/pkg/services/ngalert/remote/client/mimir.go index b74d1df91fe..6db6147ab99 100644 --- a/pkg/services/ngalert/remote/client/mimir.go +++ b/pkg/services/ngalert/remote/client/mimir.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/grafana/grafana/pkg/infra/log" + apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/client" "github.com/grafana/grafana/pkg/services/ngalert/metrics" ) @@ -23,7 +24,7 @@ type MimirClient interface { DeleteGrafanaAlertmanagerState(ctx context.Context) error GetGrafanaAlertmanagerConfig(ctx context.Context) (*UserGrafanaConfig, error) - CreateGrafanaAlertmanagerConfig(ctx context.Context, configuration, hash string, createdAt int64, isDefault bool) error + CreateGrafanaAlertmanagerConfig(ctx context.Context, configuration *apimodels.PostableUserConfig, hash string, createdAt int64, isDefault bool) error DeleteGrafanaAlertmanagerConfig(ctx context.Context) error } diff --git a/scripts/drone/utils/images.star b/scripts/drone/utils/images.star index b8946c746d3..da76d8ce306 100644 --- a/scripts/drone/utils/images.star +++ b/scripts/drone/utils/images.star @@ -21,7 +21,7 @@ images = { "plugins_slack": "plugins/slack", "python": "python:3.8", "postgres_alpine": "postgres:12.3-alpine", - "mimir": "us.gcr.io/kubernetes-dev/mimir:santihernandezc-remove_id_from_grafana_config-d3826b4f8-WIP", + "mimir": "us.gcr.io/kubernetes-dev/mimir:santihernandezc-validate_grafana_am_config-1e903e462-WIP", "mysql5": "mysql:5.7.39", "mysql8": "mysql:8.0.32", "redis_alpine": "redis:6.2.11-alpine", From bc97d11220ad3efde9784034cf4f90156f0e82c6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 10:25:34 +0000 Subject: [PATCH 16/17] Update dependency @types/eslint to v8.56.10 --- package.json | 2 +- packages/grafana-prometheus/package.json | 2 +- yarn.lock | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 4fafcc676cf..b6b98198908 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "@types/d3-scale-chromatic": "3.0.3", "@types/debounce-promise": "3.1.9", "@types/diff": "^5", - "@types/eslint": "8.56.9", + "@types/eslint": "8.56.10", "@types/eslint-scope": "^3.7.7", "@types/file-saver": "2.0.7", "@types/glob": "^8.0.0", diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index bafc259fcb9..799338563af 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -88,7 +88,7 @@ "@testing-library/user-event": "14.5.2", "@types/d3": "7.4.3", "@types/debounce-promise": "3.1.9", - "@types/eslint": "8.56.9", + "@types/eslint": "8.56.10", "@types/jest": "29.5.12", "@types/jquery": "3.5.29", "@types/lodash": "4.17.0", diff --git a/yarn.lock b/yarn.lock index 9e7bb44024f..af841ba276a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4025,7 +4025,7 @@ __metadata: "@testing-library/user-event": "npm:14.5.2" "@types/d3": "npm:7.4.3" "@types/debounce-promise": "npm:3.1.9" - "@types/eslint": "npm:8.56.9" + "@types/eslint": "npm:8.56.10" "@types/jest": "npm:29.5.12" "@types/jquery": "npm:3.5.29" "@types/lodash": "npm:4.17.0" @@ -9626,13 +9626,13 @@ __metadata: languageName: node linkType: hard -"@types/eslint@npm:*, @types/eslint@npm:8.56.9, @types/eslint@npm:^8.56.5": - version: 8.56.9 - resolution: "@types/eslint@npm:8.56.9" +"@types/eslint@npm:*, @types/eslint@npm:8.56.10, @types/eslint@npm:^8.56.5": + version: 8.56.10 + resolution: "@types/eslint@npm:8.56.10" dependencies: "@types/estree": "npm:*" "@types/json-schema": "npm:*" - checksum: 10/fde20e8f3e5384f0ac78897b04cbaf1c78f4ba6cbdae9aeba876c00b665b498670cfcdf84a39eb4e44a6e27d6de80e24b5833d51a09d5d7e410229feb8b9c401 + checksum: 10/0cdd914b944ebba51c35827d3ef95bc3e16eb82b4c2741f6437fa57cdb00a4407c77f89c220afe9e4c9566982ec8a0fb9b97c956ac3bd4623a3b6af32eed8424 languageName: node linkType: hard @@ -18681,7 +18681,7 @@ __metadata: "@types/d3-scale-chromatic": "npm:3.0.3" "@types/debounce-promise": "npm:3.1.9" "@types/diff": "npm:^5" - "@types/eslint": "npm:8.56.9" + "@types/eslint": "npm:8.56.10" "@types/eslint-scope": "npm:^3.7.7" "@types/file-saver": "npm:2.0.7" "@types/glob": "npm:^8.0.0" From c2f3bf677d08c89253595c61811e94cbb71a4bed Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 11:22:13 +0000 Subject: [PATCH 17/17] Update dependency @types/react to v18.2.79 --- package.json | 2 +- packages/grafana-data/package.json | 2 +- packages/grafana-flamegraph/package.json | 2 +- packages/grafana-icons/package.json | 2 +- .../grafana-o11y-ds-frontend/package.json | 2 +- packages/grafana-prometheus/package.json | 2 +- packages/grafana-runtime/package.json | 2 +- packages/grafana-sql/package.json | 2 +- packages/grafana-ui/package.json | 2 +- .../datasource/azuremonitor/package.json | 2 +- .../datasource/cloud-monitoring/package.json | 2 +- .../grafana-pyroscope-datasource/package.json | 2 +- .../grafana-testdata-datasource/package.json | 2 +- .../app/plugins/datasource/parca/package.json | 2 +- .../app/plugins/datasource/tempo/package.json | 2 +- .../plugins/datasource/zipkin/package.json | 2 +- yarn.lock | 40 +++++++++---------- 17 files changed, 36 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index b6b98198908..eeb40a72e9d 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,7 @@ "@types/papaparse": "5.3.14", "@types/pluralize": "^0.0.33", "@types/prismjs": "1.26.3", - "@types/react": "18.2.78", + "@types/react": "18.2.79", "@types/react-beautiful-dnd": "13.1.8", "@types/react-dom": "18.2.25", "@types/react-grid-layout": "1.3.5", diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index b720fb5e97b..fa03bb147f9 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -68,7 +68,7 @@ "@types/lodash": "4.17.0", "@types/node": "20.12.7", "@types/papaparse": "5.3.14", - "@types/react": "18.2.78", + "@types/react": "18.2.79", "@types/react-dom": "18.2.25", "@types/tinycolor2": "1.4.6", "esbuild": "0.18.12", diff --git a/packages/grafana-flamegraph/package.json b/packages/grafana-flamegraph/package.json index 93ba6b7cdd1..aec4c058d74 100644 --- a/packages/grafana-flamegraph/package.json +++ b/packages/grafana-flamegraph/package.json @@ -67,7 +67,7 @@ "@types/d3": "^7", "@types/jest": "^29.5.4", "@types/lodash": "4.17.0", - "@types/react": "18.2.78", + "@types/react": "18.2.79", "@types/react-virtualized-auto-sizer": "1.0.4", "@types/tinycolor2": "1.4.6", "babel-jest": "29.7.0", diff --git a/packages/grafana-icons/package.json b/packages/grafana-icons/package.json index b63c9866b9b..f2a8dcee0f3 100644 --- a/packages/grafana-icons/package.json +++ b/packages/grafana-icons/package.json @@ -43,7 +43,7 @@ "@svgr/plugin-prettier": "^8.1.0", "@svgr/plugin-svgo": "^8.1.0", "@types/node": "20.12.7", - "@types/react": "18.2.78", + "@types/react": "18.2.79", "@types/react-dom": "18.2.25", "esbuild": "0.18.12", "prettier": "3.2.5", diff --git a/packages/grafana-o11y-ds-frontend/package.json b/packages/grafana-o11y-ds-frontend/package.json index 82bf0ac3aec..24e31cb00fe 100644 --- a/packages/grafana-o11y-ds-frontend/package.json +++ b/packages/grafana-o11y-ds-frontend/package.json @@ -34,7 +34,7 @@ "@testing-library/react": "15.0.2", "@testing-library/user-event": "14.5.2", "@types/jest": "^29.5.4", - "@types/react": "18.2.78", + "@types/react": "18.2.79", "@types/systemjs": "6.13.5", "@types/testing-library__jest-dom": "5.14.9", "jest": "^29.6.4", diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index 799338563af..5c360add184 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -95,7 +95,7 @@ "@types/node": "20.12.7", "@types/pluralize": "^0.0.33", "@types/prismjs": "1.26.3", - "@types/react": "18.2.78", + "@types/react": "18.2.79", "@types/react-beautiful-dnd": "13.1.8", "@types/react-dom": "18.2.25", "@types/react-highlight-words": "0.16.7", diff --git a/packages/grafana-runtime/package.json b/packages/grafana-runtime/package.json index 9b341226b25..0b40a487e4a 100644 --- a/packages/grafana-runtime/package.json +++ b/packages/grafana-runtime/package.json @@ -58,7 +58,7 @@ "@types/history": "4.7.11", "@types/jest": "29.5.12", "@types/lodash": "4.17.0", - "@types/react": "18.2.78", + "@types/react": "18.2.79", "@types/react-dom": "18.2.25", "@types/systemjs": "6.13.5", "esbuild": "0.18.12", diff --git a/packages/grafana-sql/package.json b/packages/grafana-sql/package.json index 39b1bfebc0d..749c8f356d5 100644 --- a/packages/grafana-sql/package.json +++ b/packages/grafana-sql/package.json @@ -39,7 +39,7 @@ "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "14.5.2", "@types/jest": "^29.5.4", - "@types/react": "18.2.78", + "@types/react": "18.2.79", "@types/systemjs": "6.13.5", "@types/testing-library__jest-dom": "5.14.9", "jest": "^29.6.4", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 9e88abab438..eb2deed0421 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -141,7 +141,7 @@ "@types/mock-raf": "1.0.6", "@types/node": "20.12.7", "@types/prismjs": "1.26.3", - "@types/react": "18.2.78", + "@types/react": "18.2.79", "@types/react-beautiful-dnd": "13.1.8", "@types/react-color": "3.0.12", "@types/react-dom": "18.2.25", diff --git a/public/app/plugins/datasource/azuremonitor/package.json b/public/app/plugins/datasource/azuremonitor/package.json index d144ae17556..e663a544930 100644 --- a/public/app/plugins/datasource/azuremonitor/package.json +++ b/public/app/plugins/datasource/azuremonitor/package.json @@ -31,7 +31,7 @@ "@types/lodash": "4.17.0", "@types/node": "20.12.7", "@types/prismjs": "1.26.3", - "@types/react": "18.2.78", + "@types/react": "18.2.79", "@types/testing-library__jest-dom": "5.14.9", "react-select-event": "5.5.1", "ts-node": "10.9.2", diff --git a/public/app/plugins/datasource/cloud-monitoring/package.json b/public/app/plugins/datasource/cloud-monitoring/package.json index 0bac9dcda92..722924daac7 100644 --- a/public/app/plugins/datasource/cloud-monitoring/package.json +++ b/public/app/plugins/datasource/cloud-monitoring/package.json @@ -33,7 +33,7 @@ "@types/lodash": "4.17.0", "@types/node": "20.12.7", "@types/prismjs": "1.26.3", - "@types/react": "18.2.78", + "@types/react": "18.2.79", "@types/react-test-renderer": "18.0.7", "@types/testing-library__jest-dom": "5.14.9", "react-select-event": "5.5.1", diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json index 08737c4f2b3..daf6dae7647 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json @@ -27,7 +27,7 @@ "@types/jest": "29.5.12", "@types/lodash": "4.17.0", "@types/prismjs": "1.26.3", - "@types/react": "18.2.78", + "@types/react": "18.2.79", "@types/react-dom": "18.2.25", "@types/testing-library__jest-dom": "5.14.9", "css-loader": "6.10.0", diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/package.json b/public/app/plugins/datasource/grafana-testdata-datasource/package.json index 2f5f5f4559d..30af532cc33 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/package.json +++ b/public/app/plugins/datasource/grafana-testdata-datasource/package.json @@ -28,7 +28,7 @@ "@types/jest": "29.5.12", "@types/lodash": "4.17.0", "@types/node": "20.12.7", - "@types/react": "18.2.78", + "@types/react": "18.2.79", "@types/testing-library__jest-dom": "5.14.9", "@types/uuid": "9.0.8", "ts-node": "10.9.2", diff --git a/public/app/plugins/datasource/parca/package.json b/public/app/plugins/datasource/parca/package.json index 74f43a29441..6e9e05609f9 100644 --- a/public/app/plugins/datasource/parca/package.json +++ b/public/app/plugins/datasource/parca/package.json @@ -21,7 +21,7 @@ "@testing-library/react": "15.0.2", "@testing-library/user-event": "14.5.2", "@types/lodash": "4.17.0", - "@types/react": "18.2.78", + "@types/react": "18.2.79", "ts-node": "10.9.2", "webpack": "5.91.0" }, diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json index 9b918623f88..b4893668436 100644 --- a/public/app/plugins/datasource/tempo/package.json +++ b/public/app/plugins/datasource/tempo/package.json @@ -46,7 +46,7 @@ "@types/lodash": "4.17.0", "@types/node": "20.12.7", "@types/prismjs": "1.26.3", - "@types/react": "18.2.78", + "@types/react": "18.2.79", "@types/react-dom": "18.2.25", "@types/semver": "7.5.8", "@types/uuid": "9.0.8", diff --git a/public/app/plugins/datasource/zipkin/package.json b/public/app/plugins/datasource/zipkin/package.json index 1b63941215e..784d41aebeb 100644 --- a/public/app/plugins/datasource/zipkin/package.json +++ b/public/app/plugins/datasource/zipkin/package.json @@ -22,7 +22,7 @@ "@testing-library/react": "15.0.2", "@types/jest": "29.5.12", "@types/lodash": "4.17.0", - "@types/react": "18.2.78", + "@types/react": "18.2.79", "ts-node": "10.9.2", "webpack": "5.91.0" }, diff --git a/yarn.lock b/yarn.lock index af841ba276a..16df3691986 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3317,7 +3317,7 @@ __metadata: "@types/lodash": "npm:4.17.0" "@types/node": "npm:20.12.7" "@types/prismjs": "npm:1.26.3" - "@types/react": "npm:18.2.78" + "@types/react": "npm:18.2.79" "@types/testing-library__jest-dom": "npm:5.14.9" fast-deep-equal: "npm:^3.1.3" i18next: "npm:^23.0.0" @@ -3354,7 +3354,7 @@ __metadata: "@types/jest": "npm:29.5.12" "@types/lodash": "npm:4.17.0" "@types/prismjs": "npm:1.26.3" - "@types/react": "npm:18.2.78" + "@types/react": "npm:18.2.79" "@types/react-dom": "npm:18.2.25" "@types/testing-library__jest-dom": "npm:5.14.9" css-loader: "npm:6.10.0" @@ -3395,7 +3395,7 @@ __metadata: "@types/jest": "npm:29.5.12" "@types/lodash": "npm:4.17.0" "@types/node": "npm:20.12.7" - "@types/react": "npm:18.2.78" + "@types/react": "npm:18.2.79" "@types/testing-library__jest-dom": "npm:5.14.9" "@types/uuid": "npm:9.0.8" d3-random: "npm:^3.0.1" @@ -3459,7 +3459,7 @@ __metadata: "@testing-library/react": "npm:15.0.2" "@testing-library/user-event": "npm:14.5.2" "@types/lodash": "npm:4.17.0" - "@types/react": "npm:18.2.78" + "@types/react": "npm:18.2.79" lodash: "npm:4.17.21" monaco-editor: "npm:0.34.1" react: "npm:18.2.0" @@ -3493,7 +3493,7 @@ __metadata: "@types/lodash": "npm:4.17.0" "@types/node": "npm:20.12.7" "@types/prismjs": "npm:1.26.3" - "@types/react": "npm:18.2.78" + "@types/react": "npm:18.2.79" "@types/react-test-renderer": "npm:18.0.7" "@types/testing-library__jest-dom": "npm:5.14.9" debounce-promise: "npm:3.1.2" @@ -3545,7 +3545,7 @@ __metadata: "@types/lodash": "npm:4.17.0" "@types/node": "npm:20.12.7" "@types/prismjs": "npm:1.26.3" - "@types/react": "npm:18.2.78" + "@types/react": "npm:18.2.79" "@types/react-dom": "npm:18.2.25" "@types/semver": "npm:7.5.8" "@types/uuid": "npm:9.0.8" @@ -3590,7 +3590,7 @@ __metadata: "@testing-library/react": "npm:15.0.2" "@types/jest": "npm:29.5.12" "@types/lodash": "npm:4.17.0" - "@types/react": "npm:18.2.78" + "@types/react": "npm:18.2.79" lodash: "npm:4.17.21" react: "npm:18.2.0" react-use: "npm:17.5.0" @@ -3636,7 +3636,7 @@ __metadata: "@types/lodash": "npm:4.17.0" "@types/node": "npm:20.12.7" "@types/papaparse": "npm:5.3.14" - "@types/react": "npm:18.2.78" + "@types/react": "npm:18.2.79" "@types/react-dom": "npm:18.2.25" "@types/string-hash": "npm:1.1.3" "@types/tinycolor2": "npm:1.4.6" @@ -3867,7 +3867,7 @@ __metadata: "@types/d3": "npm:^7" "@types/jest": "npm:^29.5.4" "@types/lodash": "npm:4.17.0" - "@types/react": "npm:18.2.78" + "@types/react": "npm:18.2.79" "@types/react-virtualized-auto-sizer": "npm:1.0.4" "@types/tinycolor2": "npm:1.4.6" babel-jest: "npm:29.7.0" @@ -3948,7 +3948,7 @@ __metadata: "@testing-library/react": "npm:15.0.2" "@testing-library/user-event": "npm:14.5.2" "@types/jest": "npm:^29.5.4" - "@types/react": "npm:18.2.78" + "@types/react": "npm:18.2.79" "@types/systemjs": "npm:6.13.5" "@types/testing-library__jest-dom": "npm:5.14.9" jest: "npm:^29.6.4" @@ -4032,7 +4032,7 @@ __metadata: "@types/node": "npm:20.12.7" "@types/pluralize": "npm:^0.0.33" "@types/prismjs": "npm:1.26.3" - "@types/react": "npm:18.2.78" + "@types/react": "npm:18.2.79" "@types/react-beautiful-dnd": "npm:13.1.8" "@types/react-dom": "npm:18.2.25" "@types/react-highlight-words": "npm:0.16.7" @@ -4125,7 +4125,7 @@ __metadata: "@types/history": "npm:4.7.11" "@types/jest": "npm:29.5.12" "@types/lodash": "npm:4.17.0" - "@types/react": "npm:18.2.78" + "@types/react": "npm:18.2.79" "@types/react-dom": "npm:18.2.25" "@types/systemjs": "npm:6.13.5" esbuild: "npm:0.18.12" @@ -4161,7 +4161,7 @@ __metadata: "@svgr/plugin-prettier": "npm:^8.1.0" "@svgr/plugin-svgo": "npm:^8.1.0" "@types/node": "npm:20.12.7" - "@types/react": "npm:18.2.78" + "@types/react": "npm:18.2.79" "@types/react-dom": "npm:18.2.25" esbuild: "npm:0.18.12" prettier: "npm:3.2.5" @@ -4235,7 +4235,7 @@ __metadata: "@testing-library/user-event": "npm:14.5.2" "@types/jest": "npm:^29.5.4" "@types/lodash": "npm:4.17.0" - "@types/react": "npm:18.2.78" + "@types/react": "npm:18.2.79" "@types/react-virtualized-auto-sizer": "npm:1.0.4" "@types/systemjs": "npm:6.13.5" "@types/testing-library__jest-dom": "npm:5.14.9" @@ -4322,7 +4322,7 @@ __metadata: "@types/mock-raf": "npm:1.0.6" "@types/node": "npm:20.12.7" "@types/prismjs": "npm:1.26.3" - "@types/react": "npm:18.2.78" + "@types/react": "npm:18.2.79" "@types/react-beautiful-dnd": "npm:13.1.8" "@types/react-color": "npm:3.0.12" "@types/react-dom": "npm:18.2.25" @@ -10283,13 +10283,13 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:*, @types/react@npm:18.2.78, @types/react@npm:>=16": - version: 18.2.78 - resolution: "@types/react@npm:18.2.78" +"@types/react@npm:*, @types/react@npm:18.2.79, @types/react@npm:>=16": + version: 18.2.79 + resolution: "@types/react@npm:18.2.79" dependencies: "@types/prop-types": "npm:*" csstype: "npm:^3.0.2" - checksum: 10/a4bf8104c580fab40535cc6058425ac6a47c19b8dad45b566cdb38aaafa3ffa1f24e7b65ec95e391a6c18ccf4a33738a591c1f723c00300f13d61fc456a1eb8d + checksum: 10/2ef833e7d0a5c226beddbbe090811582371f6ae5e2f092a3d9f47cc6087c8bce0b96ee33e351de6d1d470f0a0ec5892d971933f841ef31538c1821681fc6569e languageName: node linkType: hard @@ -18703,7 +18703,7 @@ __metadata: "@types/papaparse": "npm:5.3.14" "@types/pluralize": "npm:^0.0.33" "@types/prismjs": "npm:1.26.3" - "@types/react": "npm:18.2.78" + "@types/react": "npm:18.2.79" "@types/react-beautiful-dnd": "npm:13.1.8" "@types/react-dom": "npm:18.2.25" "@types/react-grid-layout": "npm:1.3.5"