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 8581062e27e..8265d58efcc 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -54,6 +54,7 @@ Some features are enabled by default. You can disable these feature by setting t | `lokiStructuredMetadata` | Enables the loki data source to request structured metadata from the Loki server | Yes | | `managedPluginsInstall` | Install managed plugins directly from plugins catalog | Yes | | `annotationPermissionUpdate` | Change the way annotation permissions work by scoping them to folders and dashboards. | Yes | +| `exploreMetrics` | Enables the new Explore Metrics core app | Yes | | `alertingSimplifiedRouting` | Enables users to easily configure alert notifications by specifying a contact point directly when editing or creating an alert rule | Yes | | `logRowsPopoverMenu` | Enable filtering menu displayed when text of a log line is selected | Yes | | `lokiQueryHints` | Enables query hints for Loki | Yes | diff --git a/go.mod b/go.mod index 1f2720d1c1f..ee33acc59e1 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( github.com/google/uuid v1.6.0 // @grafana/backend-platform github.com/google/wire v0.5.0 // @grafana/backend-platform github.com/gorilla/websocket v1.5.0 // @grafana/grafana-app-platform-squad - github.com/grafana/alerting v0.0.0-20240405171255-923446b51c2d // @grafana/alerting-squad-backend + github.com/grafana/alerting v0.0.0-20240409171830-e039a7f57a92 // @grafana/alerting-squad-backend github.com/grafana/cuetsy v0.1.11 // @grafana/grafana-as-code github.com/grafana/grafana-aws-sdk v0.25.0 // @grafana/aws-datasources github.com/grafana/grafana-azure-sdk-go/v2 v2.0.1 // @grafana/partner-datasources diff --git a/go.sum b/go.sum index cfb06496152..4d5f4c58f4c 100644 --- a/go.sum +++ b/go.sum @@ -2159,8 +2159,8 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/alerting v0.0.0-20240405171255-923446b51c2d h1:wu7A06T+xFTDGUfrR6oVdM3k+vp1MLRjsXd7dckHfGA= -github.com/grafana/alerting v0.0.0-20240405171255-923446b51c2d/go.mod h1:TfSmMRify3r/yFbVM7tJGT44qUkBQ3ZWka8jwSUHQL4= +github.com/grafana/alerting v0.0.0-20240409171830-e039a7f57a92 h1:DfzT76QLAyvJZiQMEnHTRRkQsvOF0S2jH9bO1OIX5yw= +github.com/grafana/alerting v0.0.0-20240409171830-e039a7f57a92/go.mod h1:jriidrGFnZWvAn3xw/BOoCkJbGWnncuojMi9jq6xxwE= github.com/grafana/authlib v0.0.0-20240328140636-a7388d0bac72 h1:lGEuhD/KhhN1OiPrvwQejl9Lg8MvaHdj3lHZNref4is= github.com/grafana/authlib v0.0.0-20240328140636-a7388d0bac72/go.mod h1:86rRD5P6u2JPWtNWTMOlqlU+YMv2fUvVz/DomA6L7w4= github.com/grafana/codejen v0.0.3 h1:tAWxoTUuhgmEqxJPOLtJoxlPBbMULFwKFOcRsPRPXDw= diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 0855802955f..ad4e2bed13b 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -149,7 +149,7 @@ export interface FeatureToggles { canvasPanelPanZoom?: boolean; logsInfiniteScrolling?: boolean; flameGraphItemCollapsing?: boolean; - datatrails?: boolean; + exploreMetrics?: boolean; alertingSimplifiedRouting?: boolean; logRowsPopoverMenu?: boolean; pluginsSkipHostEnvVars?: boolean; diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index d2513c8141b..c18f2f2441a 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -972,12 +972,12 @@ var ( Owner: grafanaObservabilityTracesAndProfilingSquad, }, { - Name: "datatrails", - Description: "Enables the new core app datatrails", - Stage: FeatureStageExperimental, + Name: "exploreMetrics", + Description: "Enables the new Explore Metrics core app", + Stage: FeatureStageGeneralAvailability, + Expression: "true", // enabled by default FrontendOnly: true, Owner: grafanaDashboardsSquad, - HideFromDocs: true, }, { Name: "alertingSimplifiedRouting", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 093a0ebe1fa..227dbbb1b4e 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -130,7 +130,7 @@ ssoSettingsApi,preview,@grafana/identity-access-team,false,false,false canvasPanelPanZoom,preview,@grafana/dataviz-squad,false,false,true logsInfiniteScrolling,experimental,@grafana/observability-logs,false,false,true flameGraphItemCollapsing,experimental,@grafana/observability-traces-and-profiling,false,false,true -datatrails,experimental,@grafana/dashboards-squad,false,false,true +exploreMetrics,GA,@grafana/dashboards-squad,false,false,true alertingSimplifiedRouting,GA,@grafana/alerting-squad,false,false,false logRowsPopoverMenu,GA,@grafana/observability-logs,false,false,true pluginsSkipHostEnvVars,experimental,@grafana/plugins-platform-backend,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 8d5a835b3d3..7d761e3e9b9 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -531,9 +531,9 @@ const ( // Allow collapsing of flame graph items FlagFlameGraphItemCollapsing = "flameGraphItemCollapsing" - // FlagDatatrails - // Enables the new core app datatrails - FlagDatatrails = "datatrails" + // FlagExploreMetrics + // Enables the new Explore Metrics core app + FlagExploreMetrics = "exploreMetrics" // FlagAlertingSimplifiedRouting // Enables users to easily configure alert notifications by specifying a contact point directly when editing or creating an alert rule diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 5855736f83a..bde75a1e36f 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -5,62 +5,47 @@ "items": [ { "metadata": { - "name": "dashboardScene", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "name": "awsAsyncQueryCaching", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { - "description": "Enables dashboard rendering using scenes for all roles", - "stage": "experimental", - "codeowner": "@grafana/dashboards-squad", - "frontend": true - } - }, - { - "metadata": { - "name": "angularDeprecationUI", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Display Angular warnings in dashboards and panels", + "description": "Enable caching for async queries for Redshift and Athena. Requires that the datasource has caching and async query support enabled", "stage": "GA", - "codeowner": "@grafana/plugins-platform-backend", - "frontend": true + "codeowner": "@grafana/aws-datasources" } }, { "metadata": { - "name": "kubernetesPlaylists", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "name": "panelTitleSearchInV1", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { - "description": "Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s", + "description": "Enable searching for dashboards using panel title in search v1", "stage": "experimental", - "codeowner": "@grafana/grafana-app-platform-squad", - "requiresRestart": true + "codeowner": "@grafana/backend-platform", + "requiresDevMode": true } }, { "metadata": { - "name": "kubernetesFeatureToggles", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "name": "featureHighlights", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { - "description": "Use the kubernetes API for feature toggle management in the frontend", - "stage": "experimental", - "codeowner": "@grafana/grafana-operator-experience-squad", - "frontend": true, - "hideFromAdminPage": true + "description": "Highlight Grafana Enterprise features", + "stage": "GA", + "codeowner": "@grafana/grafana-as-code", + "allowSelfServe": true } }, { "metadata": { "name": "lokiQuerySplitting", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { "description": "Split large interval queries into subqueries with smaller time intervals", @@ -70,1427 +55,11 @@ "allowSelfServe": true } }, - { - "metadata": { - "name": "alertingSimplifiedRouting", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables users to easily configure alert notifications by specifying a contact point directly when editing or creating an alert rule", - "stage": "GA", - "codeowner": "@grafana/alerting-squad" - } - }, - { - "metadata": { - "name": "kubernetesQueryServiceRewrite", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Rewrite requests targeting /ds/query to the query service", - "stage": "experimental", - "codeowner": "@grafana/grafana-app-platform-squad", - "requiresDevMode": true, - "requiresRestart": true - } - }, - { - "metadata": { - "name": "lokiQueryHints", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables query hints for Loki", - "stage": "GA", - "codeowner": "@grafana/observability-logs", - "frontend": true - } - }, - { - "metadata": { - "name": "nodeGraphDotLayout", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Changed the layout algorithm for the node graph", - "stage": "experimental", - "codeowner": "@grafana/observability-traces-and-profiling", - "frontend": true - } - }, - { - "metadata": { - "name": "enableElasticsearchBackendQuerying", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enable the processing of queries and responses in the Elasticsearch data source through backend", - "stage": "GA", - "codeowner": "@grafana/observability-logs", - "allowSelfServe": true - } - }, - { - "metadata": { - "name": "alertingNoDataErrorExecution", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Changes how Alerting state manager handles execution of NoData/Error", - "stage": "GA", - "codeowner": "@grafana/alerting-squad", - "requiresRestart": true - } - }, - { - "metadata": { - "name": "alertmanagerRemoteOnly", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Disable the internal Alertmanager and only use the external one defined.", - "stage": "experimental", - "codeowner": "@grafana/alerting-squad" - } - }, - { - "metadata": { - "name": "prometheusMetricEncyclopedia", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Adds the metrics explorer component to the Prometheus query builder as an option in metric select", - "stage": "GA", - "codeowner": "@grafana/observability-metrics", - "frontend": true, - "allowSelfServe": true - } - }, - { - "metadata": { - "name": "disableSSEDataplane", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Disables dataplane specific processing in server side expressions.", - "stage": "experimental", - "codeowner": "@grafana/observability-metrics" - } - }, - { - "metadata": { - "name": "featureToggleAdminPage", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enable admin page for managing feature toggles from the Grafana front-end", - "stage": "experimental", - "codeowner": "@grafana/grafana-operator-experience-squad", - "requiresRestart": true - } - }, - { - "metadata": { - "name": "sseGroupByDatasource", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Send query to the same datasource in a single request when using server side expressions. The `cloudWatchBatchQueries` feature toggle should be enabled if this used with CloudWatch.", - "stage": "experimental", - "codeowner": "@grafana/observability-metrics" - } - }, - { - "metadata": { - "name": "pluginsAPIMetrics", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Sends metrics of public grafana packages usage by plugins", - "stage": "experimental", - "codeowner": "@grafana/plugins-platform-backend", - "frontend": true - } - }, - { - "metadata": { - "name": "teamHttpHeaders", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables Team LBAC for datasources to apply team headers to the client requests", - "stage": "preview", - "codeowner": "@grafana/identity-access-team" - } - }, - { - "metadata": { - "name": "extractFieldsNameDeduplication", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Make sure extracted field names are unique in the dataframe", - "stage": "experimental", - "codeowner": "@grafana/dataviz-squad", - "frontend": true - } - }, - { - "metadata": { - "name": "newFolderPicker", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables the nested folder picker without having nested folders enabled", - "stage": "experimental", - "codeowner": "@grafana/grafana-frontend-platform", - "frontend": true - } - }, - { - "metadata": { - "name": "nestedFolderPicker", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables the new folder picker to work with nested folders. Requires the nestedFolders feature toggle", - "stage": "GA", - "codeowner": "@grafana/grafana-frontend-platform", - "frontend": true, - "allowSelfServe": true - } - }, - { - "metadata": { - "name": "alertStateHistoryLokiPrimary", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enable a remote Loki instance as the primary source for state history reads.", - "stage": "experimental", - "codeowner": "@grafana/alerting-squad" - } - }, - { - "metadata": { - "name": "newPDFRendering", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "New implementation for the dashboard-to-PDF rendering", - "stage": "preview", - "codeowner": "@grafana/sharing-squad" - } - }, - { - "metadata": { - "name": "prometheusIncrementalQueryInstrumentation", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Adds RudderStack events to incremental queries", - "stage": "experimental", - "codeowner": "@grafana/observability-metrics", - "frontend": true - } - }, - { - "metadata": { - "name": "enablePluginsTracingByDefault", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enable plugin tracing for all external plugins", - "stage": "GA", - "codeowner": "@grafana/plugins-platform-backend", - "requiresRestart": true - } - }, - { - "metadata": { - "name": "cloudRBACRoles", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enabled grafana cloud specific RBAC roles", - "stage": "experimental", - "codeowner": "@grafana/identity-access-team", - "requiresRestart": true, - "hideFromDocs": true - } - }, - { - "metadata": { - "name": "jitterAlertRulesWithinGroups", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Distributes alert rule evaluations more evenly over time, including spreading out rules within the same group", - "stage": "preview", - "codeowner": "@grafana/alerting-squad", - "requiresRestart": true, - "hideFromDocs": true - } - }, - { - "metadata": { - "name": "disableEnvelopeEncryption", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Disable envelope encryption (emergency only)", - "stage": "GA", - "codeowner": "@grafana/grafana-as-code", - "hideFromAdminPage": true - } - }, - { - "metadata": { - "name": "unifiedRequestLog", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Writes error logs to the request logger", - "stage": "experimental", - "codeowner": "@grafana/backend-platform" - } - }, - { - "metadata": { - "name": "formatString", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enable format string transformer", - "stage": "preview", - "codeowner": "@grafana/dataviz-squad", - "frontend": true - } - }, - { - "metadata": { - "name": "onPremToCloudMigrations", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "In-development feature that will allow users to easily migrate their on-prem Grafana instances to Grafana Cloud.", - "stage": "experimental", - "codeowner": "@grafana/grafana-operator-experience-squad" - } - }, - { - "metadata": { - "name": "influxdbRunQueriesInParallel", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables running InfluxDB Influxql queries in parallel", - "stage": "privatePreview", - "codeowner": "@grafana/observability-metrics" - } - }, - { - "metadata": { - "name": "logsExploreTableVisualisation", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "A table visualisation for logs in Explore", - "stage": "GA", - "codeowner": "@grafana/observability-logs", - "frontend": true - } - }, - { - "metadata": { - "name": "extraThemes", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables extra themes", - "stage": "experimental", - "codeowner": "@grafana/grafana-frontend-platform", - "frontend": true - } - }, - { - "metadata": { - "name": "lokiPredefinedOperations", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Adds predefined query operations to Loki query editor", - "stage": "experimental", - "codeowner": "@grafana/observability-logs", - "frontend": true - } - }, - { - "metadata": { - "name": "frontendSandboxMonitorOnly", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables monitor only in the plugin frontend sandbox (if enabled)", - "stage": "experimental", - "codeowner": "@grafana/plugins-platform-backend", - "frontend": true - } - }, - { - "metadata": { - "name": "regressionTransformation", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables regression analysis transformation", - "stage": "preview", - "codeowner": "@grafana/dataviz-squad", - "frontend": true - } - }, - { - "metadata": { - "name": "unifiedStorage", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "SQL-based k8s storage", - "stage": "experimental", - "codeowner": "@grafana/grafana-app-platform-squad", - "requiresDevMode": true, - "requiresRestart": true - } - }, - { - "metadata": { - "name": "alertStateHistoryLokiOnly", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Disable Grafana alerts from emitting annotations when a remote Loki instance is available.", - "stage": "experimental", - "codeowner": "@grafana/alerting-squad" - } - }, - { - "metadata": { - "name": "mlExpressions", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enable support for Machine Learning in server-side expressions", - "stage": "experimental", - "codeowner": "@grafana/alerting-squad" - } - }, - { - "metadata": { - "name": "grafanaAPIServerEnsureKubectlAccess", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Start an additional https handler and write kubectl options", - "stage": "experimental", - "codeowner": "@grafana/grafana-app-platform-squad", - "requiresDevMode": true, - "requiresRestart": true - } - }, - { - "metadata": { - "name": "configurableSchedulerTick", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enable changing the scheduler base interval via configuration option unified_alerting.scheduler_tick_interval", - "stage": "experimental", - "codeowner": "@grafana/alerting-squad", - "requiresRestart": true, - "hideFromDocs": true - } - }, - { - "metadata": { - "name": "panelMonitoring", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables panel monitoring through logs and measurements", - "stage": "GA", - "codeowner": "@grafana/dataviz-squad", - "frontend": true - } - }, - { - "metadata": { - "name": "autoMigrateWorldmapPanel", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Migrate old worldmap panel to supported geomap panel - broken out from autoMigrateOldPanels to enable granular tracking", - "stage": "preview", - "codeowner": "@grafana/dataviz-squad", - "frontend": true - } - }, - { - "metadata": { - "name": "logsContextDatasourceUi", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Allow datasource to provide custom UI for context view", - "stage": "GA", - "codeowner": "@grafana/observability-logs", - "frontend": true, - "allowSelfServe": true - } - }, - { - "metadata": { - "name": "prometheusPromQAIL", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Prometheus and AI/ML to assist users in creating a query", - "stage": "experimental", - "codeowner": "@grafana/observability-metrics", - "frontend": true - } - }, - { - "metadata": { - "name": "accessControlOnCall", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Access control primitives for OnCall", - "stage": "preview", - "codeowner": "@grafana/identity-access-team", - "hideFromAdminPage": true - } - }, - { - "metadata": { - "name": "lokiRunQueriesInParallel", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables running Loki queries in parallel", - "stage": "privatePreview", - "codeowner": "@grafana/observability-logs" - } - }, - { - "metadata": { - "name": "libraryPanelRBAC", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables RBAC support for library panels", - "stage": "experimental", - "codeowner": "@grafana/dashboards-squad", - "requiresRestart": true - } - }, - { - "metadata": { - "name": "cachingOptimizeSerializationMemoryUsage", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "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.", - "stage": "experimental", - "codeowner": "@grafana/grafana-operator-experience-squad" - } - }, - { - "metadata": { - "name": "groupToNestedTableTransformation", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables the group to nested table transformation", - "stage": "preview", - "codeowner": "@grafana/dataviz-squad", - "frontend": true - } - }, - { - "metadata": { - "name": "expressionParser", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enable new expression parser", - "stage": "experimental", - "codeowner": "@grafana/grafana-app-platform-squad", - "requiresRestart": true - } - }, - { - "metadata": { - "name": "oauthRequireSubClaim", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Require that sub claims is present in oauth tokens.", - "stage": "experimental", - "codeowner": "@grafana/identity-access-team", - "hideFromAdminPage": true, - "hideFromDocs": true - } - }, - { - "metadata": { - "name": "lokiQuerySplittingConfig", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Give users the option to configure split durations for Loki queries", - "stage": "experimental", - "codeowner": "@grafana/observability-logs", - "frontend": true - } - }, - { - "metadata": { - "name": "refactorVariablesTimeRange", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Refactor time range variables flow to reduce number of API calls made when query variables are chained", - "stage": "preview", - "codeowner": "@grafana/dashboards-squad", - "hideFromAdminPage": true - } - }, - { - "metadata": { - "name": "editPanelCSVDragAndDrop", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables drag and drop for CSV and Excel files", - "stage": "experimental", - "codeowner": "@grafana/dataviz-squad", - "frontend": true - } - }, - { - "metadata": { - "name": "prometheusDataplane", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Changes responses to from Prometheus to be compliant with the dataplane specification. In particular, when this feature toggle is active, the numeric `Field.Name` is set from 'Value' to the value of the `__name__` label.", - "stage": "GA", - "codeowner": "@grafana/observability-metrics", - "allowSelfServe": true - } - }, - { - "metadata": { - "name": "dashboardEmbed", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Allow embedding dashboard for external use in Code editors", - "stage": "experimental", - "codeowner": "@grafana/grafana-as-code", - "frontend": true - } - }, - { - "metadata": { - "name": "sqlDatasourceDatabaseSelection", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables previous SQL data source dataset dropdown behavior", - "stage": "preview", - "codeowner": "@grafana/dataviz-squad", - "frontend": true, - "hideFromAdminPage": true - } - }, - { - "metadata": { - "name": "transformationsVariableSupport", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Allows using variables in transformations", - "stage": "preview", - "codeowner": "@grafana/dataviz-squad", - "frontend": true - } - }, - { - "metadata": { - "name": "pdfTables", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables generating table data as PDF in reporting", - "stage": "preview", - "codeowner": "@grafana/sharing-squad" - } - }, - { - "metadata": { - "name": "publicDashboardsScene", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables public dashboard rendering using scenes", - "stage": "experimental", - "codeowner": "@grafana/sharing-squad", - "frontend": true - } - }, - { - "metadata": { - "name": "storage", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Configurable storage for dashboards, datasources, and resources", - "stage": "experimental", - "codeowner": "@grafana/grafana-app-platform-squad" - } - }, - { - "metadata": { - "name": "transformationsRedesign", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables the transformations redesign", - "stage": "GA", - "codeowner": "@grafana/observability-metrics", - "frontend": true, - "allowSelfServe": true - } - }, - { - "metadata": { - "name": "influxdbSqlSupport", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enable InfluxDB SQL query language support with new querying UI", - "stage": "GA", - "codeowner": "@grafana/observability-metrics", - "requiresRestart": true, - "allowSelfServe": true - } - }, - { - "metadata": { - "name": "addFieldFromCalculationStatFunctions", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Add cumulative and window functions to the add field from calculation transformation", - "stage": "preview", - "codeowner": "@grafana/dataviz-squad", - "frontend": true - } - }, - { - "metadata": { - "name": "logRowsPopoverMenu", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enable filtering menu displayed when text of a log line is selected", - "stage": "GA", - "codeowner": "@grafana/observability-logs", - "frontend": true - } - }, - { - "metadata": { - "name": "alertingQueryOptimization", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Optimizes eligible queries in order to reduce load on datasources", - "stage": "GA", - "codeowner": "@grafana/alerting-squad" - } - }, - { - "metadata": { - "name": "autoMigrateStatPanel", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Migrate old stat panel to supported stat panel - broken out from autoMigrateOldPanels to enable granular tracking", - "stage": "preview", - "codeowner": "@grafana/dataviz-squad", - "frontend": true - } - }, - { - "metadata": { - "name": "lokiFormatQuery", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables the ability to format Loki queries", - "stage": "experimental", - "codeowner": "@grafana/observability-logs", - "frontend": true - } - }, - { - "metadata": { - "name": "influxqlStreamingParser", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enable streaming JSON parser for InfluxDB datasource InfluxQL query language", - "stage": "experimental", - "codeowner": "@grafana/observability-metrics" - } - }, - { - "metadata": { - "name": "traceQLStreaming", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables response streaming of TraceQL queries of the Tempo data source", - "stage": "GA", - "codeowner": "@grafana/observability-traces-and-profiling", - "frontend": true - } - }, - { - "metadata": { - "name": "usePrometheusFrontendPackage", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Use the @grafana/prometheus frontend package in core Prometheus.", - "stage": "experimental", - "codeowner": "@grafana/observability-metrics", - "frontend": true - } - }, - { - "metadata": { - "name": "queryOverLive", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Use Grafana Live WebSocket to execute backend queries", - "stage": "experimental", - "codeowner": "@grafana/grafana-app-platform-squad", - "frontend": true - } - }, - { - "metadata": { - "name": "featureHighlights", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Highlight Grafana Enterprise features", - "stage": "GA", - "codeowner": "@grafana/grafana-as-code", - "allowSelfServe": true - } - }, - { - "metadata": { - "name": "returnToPrevious", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables the return to previous context functionality", - "stage": "preview", - "codeowner": "@grafana/grafana-frontend-platform", - "frontend": true - } - }, - { - "metadata": { - "name": "recordedQueriesMulti", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables writing multiple items from a single query within Recorded Queries", - "stage": "GA", - "codeowner": "@grafana/observability-metrics" - } - }, - { - "metadata": { - "name": "externalServiceAccounts", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Automatic service account and token setup for plugins", - "stage": "preview", - "codeowner": "@grafana/identity-access-team", - "hideFromAdminPage": true - } - }, - { - "metadata": { - "name": "awsDatasourcesNewFormStyling", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Applies new form styling for configuration and query editors in AWS plugins", - "stage": "preview", - "codeowner": "@grafana/aws-datasources", - "frontend": true - } - }, - { - "metadata": { - "name": "panelFilterVariable", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables use of the `systemPanelFilterVar` variable to filter panels in a dashboard", - "stage": "experimental", - "codeowner": "@grafana/dashboards-squad", - "frontend": true, - "hideFromDocs": true - } - }, - { - "metadata": { - "name": "canvasPanelPanZoom", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Allow pan and zoom in canvas panel", - "stage": "preview", - "codeowner": "@grafana/dataviz-squad", - "frontend": true - } - }, - { - "metadata": { - "name": "autoMigrateOldPanels", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Migrate old angular panels to supported versions (graph, table-old, worldmap, etc)", - "stage": "preview", - "codeowner": "@grafana/dataviz-squad", - "frontend": true - } - }, - { - "metadata": { - "name": "canvasPanelNesting", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Allow elements nesting", - "stage": "experimental", - "codeowner": "@grafana/dataviz-squad", - "frontend": true, - "hideFromAdminPage": true - } - }, - { - "metadata": { - "name": "lokiLogsDataplane", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Changes logs responses from Loki to be compliant with the dataplane specification.", - "stage": "experimental", - "codeowner": "@grafana/observability-logs" - } - }, - { - "metadata": { - "name": "annotationPermissionUpdate", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Change the way annotation permissions work by scoping them to folders and dashboards.", - "stage": "GA", - "codeowner": "@grafana/identity-access-team" - } - }, - { - "metadata": { - "name": "sqlExpressions", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables using SQL and DuckDB functions as Expressions.", - "stage": "experimental", - "codeowner": "@grafana/grafana-app-platform-squad" - } - }, - { - "metadata": { - "name": "autoMigrateGraphPanel", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Migrate old graph panel to supported time series panel - broken out from autoMigrateOldPanels to enable granular tracking", - "stage": "preview", - "codeowner": "@grafana/dataviz-squad", - "frontend": true - } - }, - { - "metadata": { - "name": "influxdbBackendMigration", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Query InfluxDB InfluxQL without the proxy", - "stage": "GA", - "codeowner": "@grafana/observability-metrics", - "frontend": true - } - }, - { - "metadata": { - "name": "grpcServer", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Run the GRPC server", - "stage": "preview", - "codeowner": "@grafana/grafana-app-platform-squad", - "hideFromAdminPage": true - } - }, - { - "metadata": { - "name": "alertingBacktesting", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Rule backtesting API for alerting", - "stage": "experimental", - "codeowner": "@grafana/alerting-squad" - } - }, - { - "metadata": { - "name": "individualCookiePreferences", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Support overriding cookie preferences per user", - "stage": "experimental", - "codeowner": "@grafana/backend-platform" - } - }, - { - "metadata": { - "name": "alertingInsights", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Show the new alerting insights landing page", - "stage": "GA", - "codeowner": "@grafana/alerting-squad", - "frontend": true, - "hideFromAdminPage": true - } - }, - { - "metadata": { - "name": "externalCorePlugins", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Allow core plugins to be loaded as external", - "stage": "experimental", - "codeowner": "@grafana/plugins-platform-backend" - } - }, - { - "metadata": { - "name": "panelTitleSearchInV1", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enable searching for dashboards using panel title in search v1", - "stage": "experimental", - "codeowner": "@grafana/backend-platform", - "requiresDevMode": true - } - }, - { - "metadata": { - "name": "publicDashboards", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "[Deprecated] Public dashboards are now enabled by default; to disable them, use the configuration setting. This feature toggle will be removed in the next major version.", - "stage": "GA", - "codeowner": "@grafana/sharing-squad", - "allowSelfServe": true - } - }, - { - "metadata": { - "name": "disableAngular", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Dynamic flag to disable angular at runtime. The preferred method is to set `angular_support_enabled` to `false` in the [security] settings, which allows you to change the state at runtime.", - "stage": "preview", - "codeowner": "@grafana/dataviz-squad", - "frontend": true, - "hideFromAdminPage": true - } - }, - { - "metadata": { - "name": "promQLScope", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "In-development feature that will allow injection of labels into prometheus queries.", - "stage": "experimental", - "codeowner": "@grafana/observability-metrics" - } - }, - { - "metadata": { - "name": "logRequestsInstrumentedAsUnknown", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Logs the path for requests that are instrumented as unknown", - "stage": "experimental", - "codeowner": "@grafana/hosted-grafana-team" - } - }, - { - "metadata": { - "name": "faroDatasourceSelector", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enable the data source selector within the Frontend Apps section of the Frontend Observability", - "stage": "preview", - "codeowner": "@grafana/app-o11y", - "frontend": true - } - }, - { - "metadata": { - "name": "pluginsFrontendSandbox", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables the plugins frontend sandbox", - "stage": "experimental", - "codeowner": "@grafana/plugins-platform-backend", - "frontend": true - } - }, - { - "metadata": { - "name": "dashboardSceneSolo", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables rendering dashboards using scenes for solo panels", - "stage": "experimental", - "codeowner": "@grafana/dashboards-squad", - "frontend": true - } - }, - { - "metadata": { - "name": "tableSharedCrosshair", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables shared crosshair in table panel", - "stage": "experimental", - "codeowner": "@grafana/dataviz-squad", - "frontend": true - } - }, - { - "metadata": { - "name": "alertingSaveStatePeriodic", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Writes the state periodically to the database, asynchronous to rule evaluation", - "stage": "privatePreview", - "codeowner": "@grafana/alerting-squad" - } - }, - { - "metadata": { - "name": "exploreContentOutline", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Content outline sidebar", - "stage": "GA", - "codeowner": "@grafana/explore-squad", - "frontend": true, - "allowSelfServe": true - } - }, - { - "metadata": { - "name": "datasourceQueryMultiStatus", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Introduce HTTP 207 Multi Status for api/ds/query", - "stage": "experimental", - "codeowner": "@grafana/plugins-platform-backend" - } - }, - { - "metadata": { - "name": "cloudWatchNewLabelParsing", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Updates CloudWatch label parsing to be more accurate", - "stage": "GA", - "codeowner": "@grafana/aws-datasources" - } - }, - { - "metadata": { - "name": "awsAsyncQueryCaching", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enable caching for async queries for Redshift and Athena. Requires that the datasource has caching and async query support enabled", - "stage": "GA", - "codeowner": "@grafana/aws-datasources" - } - }, - { - "metadata": { - "name": "aiGeneratedDashboardChanges", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enable AI powered features for dashboards to auto-summary changes when saving", - "stage": "experimental", - "codeowner": "@grafana/dashboards-squad", - "frontend": true - } - }, - { - "metadata": { - "name": "betterPageScrolling", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Removes CustomScrollbar from the UI, relying on native browser scrollbars", - "stage": "GA", - "codeowner": "@grafana/grafana-frontend-platform", - "frontend": true - } - }, - { - "metadata": { - "name": "publicDashboardsEmailSharing", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables public dashboard sharing to be restricted to only allowed emails", - "stage": "preview", - "codeowner": "@grafana/sharing-squad", - "hideFromAdminPage": true, - "hideFromDocs": true - } - }, - { - "metadata": { - "name": "nestedFolders", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enable folder nesting", - "stage": "GA", - "codeowner": "@grafana/backend-platform" - } - }, - { - "metadata": { - "name": "ssoSettingsApi", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables the SSO settings API and the OAuth configuration UIs in Grafana", - "stage": "preview", - "codeowner": "@grafana/identity-access-team", - "allowSelfServe": true - } - }, - { - "metadata": { - "name": "ssoSettingsSAML", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Use the new SSO Settings API to configure the SAML connector", - "stage": "experimental", - "codeowner": "@grafana/identity-access-team", - "hideFromAdminPage": true, - "hideFromDocs": true - } - }, - { - "metadata": { - "name": "cloudWatchBatchQueries", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Runs CloudWatch metrics queries as separate batches", - "stage": "preview", - "codeowner": "@grafana/aws-datasources" - } - }, - { - "metadata": { - "name": "managedPluginsInstall", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Install managed plugins directly from plugins catalog", - "stage": "GA", - "codeowner": "@grafana/plugins-platform-backend" - } - }, { "metadata": { "name": "logsInfiniteScrolling", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { "description": "Enables infinite scrolling for the Logs panel in Explore and Dashboards", @@ -1501,88 +70,21 @@ }, { "metadata": { - "name": "scenes", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "name": "onPremToCloudMigrations", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { - "description": "Experimental framework to build interactive dashboards", + "description": "In-development feature that will allow users to easily migrate their on-prem Grafana instances to Grafana Cloud.", "stage": "experimental", - "codeowner": "@grafana/dashboards-squad", - "frontend": true - } - }, - { - "metadata": { - "name": "reportingRetries", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables rendering retries for the reporting feature", - "stage": "preview", - "codeowner": "@grafana/sharing-squad", - "requiresRestart": true - } - }, - { - "metadata": { - "name": "scopeFilters", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables the use of scope filters in Grafana", - "stage": "experimental", - "codeowner": "@grafana/dashboards-squad", - "hideFromAdminPage": true, - "hideFromDocs": true - } - }, - { - "metadata": { - "name": "autoMigrateTablePanel", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Migrate old table panel to supported table panel - broken out from autoMigrateOldPanels to enable granular tracking", - "stage": "preview", - "codeowner": "@grafana/dataviz-squad", - "frontend": true - } - }, - { - "metadata": { - "name": "alertingNoNormalState", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Stop maintaining state of alerts that are not firing", - "stage": "preview", - "codeowner": "@grafana/alerting-squad", - "hideFromAdminPage": true - } - }, - { - "metadata": { - "name": "flameGraphItemCollapsing", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Allow collapsing of flame graph items", - "stage": "experimental", - "codeowner": "@grafana/observability-traces-and-profiling", - "frontend": true + "codeowner": "@grafana/grafana-operator-experience-squad" } }, { "metadata": { "name": "groupByVariable", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { "description": "Enable groupBy variable support in scenes dashboards", @@ -1594,173 +96,126 @@ }, { "metadata": { - "name": "lokiExperimentalStreaming", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "name": "scopeFilters", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { - "description": "Support new streaming approach for loki (prototype, needs special loki build)", + "description": "Enables the use of scope filters in Grafana", "stage": "experimental", - "codeowner": "@grafana/observability-logs" + "codeowner": "@grafana/dashboards-squad", + "hideFromAdminPage": true, + "hideFromDocs": true } }, { "metadata": { - "name": "dataplaneFrontendFallback", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "name": "prometheusIncrementalQueryInstrumentation", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { - "description": "Support dataplane contract field name change for transformations and field name matchers where the name is different", - "stage": "GA", + "description": "Adds RudderStack events to incremental queries", + "stage": "experimental", "codeowner": "@grafana/observability-metrics", - "frontend": true, - "allowSelfServe": true - } - }, - { - "metadata": { - "name": "mysqlAnsiQuotes", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Use double quotes to escape keyword in a MySQL query", - "stage": "experimental", - "codeowner": "@grafana/backend-platform" - } - }, - { - "metadata": { - "name": "renderAuthJWT", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Uses JWT-based auth for rendering instead of relying on remote cache", - "stage": "preview", - "codeowner": "@grafana/grafana-as-code", - "hideFromAdminPage": true - } - }, - { - "metadata": { - "name": "prometheusConfigOverhaulAuth", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Update the Prometheus configuration page with the new auth component", - "stage": "GA", - "codeowner": "@grafana/observability-metrics" - } - }, - { - "metadata": { - "name": "recoveryThreshold", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables feature recovery threshold (aka hysteresis) for threshold server-side expression", - "stage": "GA", - "codeowner": "@grafana/alerting-squad", - "requiresRestart": true - } - }, - { - "metadata": { - "name": "live-service-web-worker", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "This will use a webworker thread to processes events rather than the main thread", - "stage": "experimental", - "codeowner": "@grafana/grafana-app-platform-squad", "frontend": true } }, { "metadata": { - "name": "topnav", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "name": "alertmanagerRemotePrimary", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { - "description": "Enables topnav support in external plugins. The new Grafana navigation cannot be disabled.", - "stage": "deprecated", - "codeowner": "@grafana/grafana-frontend-platform" - } - }, - { - "metadata": { - "name": "metricsSummary", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables metrics summary queries in the Tempo data source", - "stage": "experimental", - "codeowner": "@grafana/observability-traces-and-profiling", - "frontend": true - } - }, - { - "metadata": { - "name": "alertmanagerRemoteSecondary", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enable Grafana to sync configuration and state with a remote Alertmanager.", + "description": "Enable Grafana to have a remote Alertmanager instance as the primary Alertmanager.", "stage": "experimental", "codeowner": "@grafana/alerting-squad" } }, { "metadata": { - "name": "dashboardSceneForViewers", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "name": "returnToPrevious", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { - "description": "Enables dashboard rendering using Scenes for viewer roles", - "stage": "experimental", - "codeowner": "@grafana/dashboards-squad", + "description": "Enables the return to previous context functionality", + "stage": "preview", + "codeowner": "@grafana/grafana-frontend-platform", "frontend": true } }, { "metadata": { - "name": "correlations", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "name": "extraThemes", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { - "description": "Correlations page", - "stage": "GA", - "codeowner": "@grafana/explore-squad", - "allowSelfServe": true + "description": "Enables extra themes", + "stage": "experimental", + "codeowner": "@grafana/grafana-frontend-platform", + "frontend": true } }, { "metadata": { - "name": "showDashboardValidationWarnings", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "name": "reportingRetries", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { - "description": "Show warnings when dashboards do not validate against the schema", + "description": "Enables rendering retries for the reporting feature", + "stage": "preview", + "codeowner": "@grafana/sharing-squad", + "requiresRestart": true + } + }, + { + "metadata": { + "name": "prometheusPromQAIL", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Prometheus and AI/ML to assist users in creating a query", "stage": "experimental", - "codeowner": "@grafana/dashboards-squad" + "codeowner": "@grafana/observability-metrics", + "frontend": true + } + }, + { + "metadata": { + "name": "panelTitleSearch", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Search for dashboards using panel title", + "stage": "preview", + "codeowner": "@grafana/grafana-app-platform-squad", + "hideFromAdminPage": true + } + }, + { + "metadata": { + "name": "autoMigrateWorldmapPanel", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Migrate old worldmap panel to supported geomap panel - broken out from autoMigrateOldPanels to enable granular tracking", + "stage": "preview", + "codeowner": "@grafana/dataviz-squad", + "frontend": true } }, { "metadata": { "name": "grafanaAPIServerWithExperimentalAPIs", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { "description": "Register experimental APIs with the k8s API server", @@ -1772,49 +227,24 @@ }, { "metadata": { - "name": "idForwarding", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "name": "prometheusConfigOverhaulAuth", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { - "description": "Generate signed id token for identity that can be forwarded to plugins and external services", - "stage": "experimental", - "codeowner": "@grafana/identity-access-team" - } - }, - { - "metadata": { - "name": "lokiStructuredMetadata", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables the loki data source to request structured metadata from the Loki server", + "description": "Update the Prometheus configuration page with the new auth component", "stage": "GA", - "codeowner": "@grafana/observability-logs" + "codeowner": "@grafana/observability-metrics" } }, { "metadata": { - "name": "kubernetesAggregator", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "name": "transformationsVariableSupport", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { - "description": "Enable grafana aggregator", - "stage": "experimental", - "codeowner": "@grafana/grafana-app-platform-squad", - "requiresRestart": true - } - }, - { - "metadata": { - "name": "autoMigrateXYChartPanel", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Migrate old XYChart panel to new XYChart2 model", + "description": "Allows using variables in transformations", "stage": "preview", "codeowner": "@grafana/dataviz-squad", "frontend": true @@ -1822,86 +252,129 @@ }, { "metadata": { - "name": "pluginsDynamicAngularDetectionPatterns", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "name": "publicDashboards", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { - "description": "Enables fetching Angular detection patterns for plugins from GCOM and fallback to hardcoded ones", + "description": "[Deprecated] Public dashboards are now enabled by default; to disable them, use the configuration setting. This feature toggle will be removed in the next major version.", "stage": "GA", - "codeowner": "@grafana/plugins-platform-backend" + "codeowner": "@grafana/sharing-squad", + "allowSelfServe": true } }, { "metadata": { - "name": "newDashboardWithFiltersAndGroupBy", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "name": "autoMigratePiechartPanel", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { - "description": "Enables filters and group by variables on all new dashboards. Variables are added only if default data source supports filtering.", - "stage": "experimental", - "codeowner": "@grafana/dashboards-squad", - "hideFromAdminPage": true, - "hideFromDocs": true + "description": "Migrate old piechart panel to supported piechart panel - broken out from autoMigrateOldPanels to enable granular tracking", + "stage": "preview", + "codeowner": "@grafana/dataviz-squad", + "frontend": true } }, { "metadata": { - "name": "awsDatasourcesTempCredentials", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "name": "kubernetesQueryServiceRewrite", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { - "description": "Support temporary security credentials in AWS plugins for Grafana Cloud customers", - "stage": "experimental", - "codeowner": "@grafana/aws-datasources" - } - }, - { - "metadata": { - "name": "datatrails", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Enables the new core app datatrails", - "stage": "experimental", - "codeowner": "@grafana/dashboards-squad", - "frontend": true, - "hideFromDocs": true - } - }, - { - "metadata": { - "name": "wargamesTesting", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Placeholder feature flag for internal testing", - "stage": "experimental", - "codeowner": "@grafana/hosted-grafana-team" - } - }, - { - "metadata": { - "name": "kubernetesSnapshots", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Routes snapshot requests from /api to the /apis endpoint", + "description": "Rewrite requests targeting /ds/query to the query service", "stage": "experimental", "codeowner": "@grafana/grafana-app-platform-squad", + "requiresDevMode": true, "requiresRestart": true } }, + { + "metadata": { + "name": "prometheusCodeModeMetricNamesSearch", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables search for metric names in Code Mode, to improve performance when working with an enormous number of metric names", + "stage": "experimental", + "codeowner": "@grafana/observability-metrics", + "frontend": true + } + }, + { + "metadata": { + "name": "tableSharedCrosshair", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables shared crosshair in table panel", + "stage": "experimental", + "codeowner": "@grafana/dataviz-squad", + "frontend": true + } + }, + { + "metadata": { + "name": "unifiedStorage", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "SQL-based k8s storage", + "stage": "experimental", + "codeowner": "@grafana/grafana-app-platform-squad", + "requiresDevMode": true, + "requiresRestart": true + } + }, + { + "metadata": { + "name": "configurableSchedulerTick", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enable changing the scheduler base interval via configuration option unified_alerting.scheduler_tick_interval", + "stage": "experimental", + "codeowner": "@grafana/alerting-squad", + "requiresRestart": true, + "hideFromDocs": true + } + }, + { + "metadata": { + "name": "extractFieldsNameDeduplication", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Make sure extracted field names are unique in the dataframe", + "stage": "experimental", + "codeowner": "@grafana/dataviz-squad", + "frontend": true + } + }, + { + "metadata": { + "name": "ssoSettingsApi", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables the SSO settings API and the OAuth configuration UIs in Grafana", + "stage": "preview", + "codeowner": "@grafana/identity-access-team", + "allowSelfServe": true + } + }, { "metadata": { "name": "lokiMetricDataplane", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { "description": "Changes metric responses from Loki to be compliant with the dataplane specification.", @@ -1912,12 +385,36 @@ }, { "metadata": { - "name": "enableDatagridEditing", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "name": "disableSSEDataplane", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { - "description": "Enables the edit functionality in the datagrid panel", + "description": "Disables dataplane specific processing in server side expressions.", + "stage": "experimental", + "codeowner": "@grafana/observability-metrics" + } + }, + { + "metadata": { + "name": "unifiedRequestLog", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Writes error logs to the request logger", + "stage": "experimental", + "codeowner": "@grafana/backend-platform" + } + }, + { + "metadata": { + "name": "autoMigrateOldPanels", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Migrate old angular panels to supported versions (graph, table-old, worldmap, etc)", "stage": "preview", "codeowner": "@grafana/dataviz-squad", "frontend": true @@ -1925,12 +422,164 @@ }, { "metadata": { - "name": "alertStateHistoryLokiSecondary", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "name": "canvasPanelNesting", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { - "description": "Enable Grafana to write alert state history to an external Loki instance in addition to Grafana annotations.", + "description": "Allow elements nesting", + "stage": "experimental", + "codeowner": "@grafana/dataviz-squad", + "frontend": true, + "hideFromAdminPage": true + } + }, + { + "metadata": { + "name": "cachingOptimizeSerializationMemoryUsage", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "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.", + "stage": "experimental", + "codeowner": "@grafana/grafana-operator-experience-squad" + } + }, + { + "metadata": { + "name": "pluginsSkipHostEnvVars", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Disables passing host environment variable to plugin processes", + "stage": "experimental", + "codeowner": "@grafana/plugins-platform-backend" + } + }, + { + "metadata": { + "name": "groupToNestedTableTransformation", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables the group to nested table transformation", + "stage": "preview", + "codeowner": "@grafana/dataviz-squad", + "frontend": true + } + }, + { + "metadata": { + "name": "cloudWatchNewLabelParsing", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Updates CloudWatch label parsing to be more accurate", + "stage": "GA", + "codeowner": "@grafana/aws-datasources" + } + }, + { + "metadata": { + "name": "publicDashboardsScene", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables public dashboard rendering using scenes", + "stage": "experimental", + "codeowner": "@grafana/sharing-squad", + "frontend": true + } + }, + { + "metadata": { + "name": "aiGeneratedDashboardChanges", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enable AI powered features for dashboards to auto-summary changes when saving", + "stage": "experimental", + "codeowner": "@grafana/dashboards-squad", + "frontend": true + } + }, + { + "metadata": { + "name": "kubernetesAggregator", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enable grafana aggregator", + "stage": "experimental", + "codeowner": "@grafana/grafana-app-platform-squad", + "requiresRestart": true + } + }, + { + "metadata": { + "name": "cloudRBACRoles", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enabled grafana cloud specific RBAC roles", + "stage": "experimental", + "codeowner": "@grafana/identity-access-team", + "requiresRestart": true, + "hideFromDocs": true + } + }, + { + "metadata": { + "name": "alertingSaveStatePeriodic", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Writes the state periodically to the database, asynchronous to rule evaluation", + "stage": "privatePreview", + "codeowner": "@grafana/alerting-squad" + } + }, + { + "metadata": { + "name": "externalCorePlugins", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Allow core plugins to be loaded as external", + "stage": "experimental", + "codeowner": "@grafana/plugins-platform-backend" + } + }, + { + "metadata": { + "name": "alertingSimplifiedRouting", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables users to easily configure alert notifications by specifying a contact point directly when editing or creating an alert rule", + "stage": "GA", + "codeowner": "@grafana/alerting-squad" + } + }, + { + "metadata": { + "name": "alertStateHistoryLokiPrimary", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enable a remote Loki instance as the primary source for state history reads.", "stage": "experimental", "codeowner": "@grafana/alerting-squad" } @@ -1938,8 +587,8 @@ { "metadata": { "name": "vizAndWidgetSplit", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { "description": "Split panels between visualizations and widgets", @@ -1950,21 +599,84 @@ }, { "metadata": { - "name": "pluginsSkipHostEnvVars", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "name": "alertingInsights", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { - "description": "Disables passing host environment variable to plugin processes", + "description": "Show the new alerting insights landing page", + "stage": "GA", + "codeowner": "@grafana/alerting-squad", + "frontend": true, + "hideFromAdminPage": true + } + }, + { + "metadata": { + "name": "cloudWatchBatchQueries", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Runs CloudWatch metrics queries as separate batches", + "stage": "preview", + "codeowner": "@grafana/aws-datasources" + } + }, + { + "metadata": { + "name": "enablePluginsTracingByDefault", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enable plugin tracing for all external plugins", + "stage": "GA", + "codeowner": "@grafana/plugins-platform-backend", + "requiresRestart": true + } + }, + { + "metadata": { + "name": "mysqlAnsiQuotes", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Use double quotes to escape keyword in a MySQL query", "stage": "experimental", - "codeowner": "@grafana/plugins-platform-backend" + "codeowner": "@grafana/backend-platform" + } + }, + { + "metadata": { + "name": "influxdbRunQueriesInParallel", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables running InfluxDB Influxql queries in parallel", + "stage": "privatePreview", + "codeowner": "@grafana/observability-metrics" + } + }, + { + "metadata": { + "name": "pdfTables", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables generating table data as PDF in reporting", + "stage": "preview", + "codeowner": "@grafana/sharing-squad" } }, { "metadata": { "name": "authAPIAccessTokenAuth", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { "description": "Enables the use of Auth API access tokens for authentication", @@ -1976,25 +688,12 @@ }, { "metadata": { - "name": "panelTitleSearch", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "name": "autoMigrateXYChartPanel", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { - "description": "Search for dashboards using panel title", - "stage": "preview", - "codeowner": "@grafana/grafana-app-platform-squad", - "hideFromAdminPage": true - } - }, - { - "metadata": { - "name": "autoMigratePiechartPanel", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" - }, - "spec": { - "description": "Migrate old piechart panel to supported piechart panel - broken out from autoMigrateOldPanels to enable granular tracking", + "description": "Migrate old XYChart panel to new XYChart2 model", "stage": "preview", "codeowner": "@grafana/dataviz-squad", "frontend": true @@ -2002,34 +701,141 @@ }, { "metadata": { - "name": "dashgpt", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "name": "libraryPanelRBAC", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { - "description": "Enable AI powered features in dashboards", - "stage": "GA", + "description": "Enables RBAC support for library panels", + "stage": "experimental", "codeowner": "@grafana/dashboards-squad", + "requiresRestart": true + } + }, + { + "metadata": { + "name": "teamHttpHeaders", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables Team LBAC for datasources to apply team headers to the client requests", + "stage": "preview", + "codeowner": "@grafana/identity-access-team" + } + }, + { + "metadata": { + "name": "panelFilterVariable", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables use of the `systemPanelFilterVar` variable to filter panels in a dashboard", + "stage": "experimental", + "codeowner": "@grafana/dashboards-squad", + "frontend": true, + "hideFromDocs": true + } + }, + { + "metadata": { + "name": "newDashboardWithFiltersAndGroupBy", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables filters and group by variables on all new dashboards. Variables are added only if default data source supports filtering.", + "stage": "experimental", + "codeowner": "@grafana/dashboards-squad", + "hideFromAdminPage": true, + "hideFromDocs": true + } + }, + { + "metadata": { + "name": "featureToggleAdminPage", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enable admin page for managing feature toggles from the Grafana front-end", + "stage": "experimental", + "codeowner": "@grafana/grafana-operator-experience-squad", + "requiresRestart": true + } + }, + { + "metadata": { + "name": "angularDeprecationUI", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Display Angular warnings in dashboards and panels", + "stage": "GA", + "codeowner": "@grafana/plugins-platform-backend", "frontend": true } }, { "metadata": { - "name": "enableNativeHTTPHistogram", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "name": "grafanaAPIServerEnsureKubectlAccess", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { - "description": "Enables native HTTP Histograms", + "description": "Start an additional https handler and write kubectl options", "stage": "experimental", - "codeowner": "@grafana/hosted-grafana-team" + "codeowner": "@grafana/grafana-app-platform-squad", + "requiresDevMode": true, + "requiresRestart": true + } + }, + { + "metadata": { + "name": "kubernetesSnapshots", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Routes snapshot requests from /api to the /apis endpoint", + "stage": "experimental", + "codeowner": "@grafana/grafana-app-platform-squad", + "requiresRestart": true + } + }, + { + "metadata": { + "name": "recoveryThreshold", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables feature recovery threshold (aka hysteresis) for threshold server-side expression", + "stage": "GA", + "codeowner": "@grafana/alerting-squad", + "requiresRestart": true + } + }, + { + "metadata": { + "name": "betterPageScrolling", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Removes CustomScrollbar from the UI, relying on native browser scrollbars", + "stage": "GA", + "codeowner": "@grafana/grafana-frontend-platform", + "frontend": true } }, { "metadata": { "name": "cloudWatchCrossAccountQuerying", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { "description": "Enables cross-account querying in CloudWatch datasources", @@ -2040,21 +846,779 @@ }, { "metadata": { - "name": "permissionsFilterRemoveSubquery", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "name": "lokiQuerySplittingConfig", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { - "description": "Alternative permission filter implementation that does not use subqueries for fetching the dashboard folder", + "description": "Give users the option to configure split durations for Loki queries", "stage": "experimental", + "codeowner": "@grafana/observability-logs", + "frontend": true + } + }, + { + "metadata": { + "name": "recordedQueriesMulti", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables writing multiple items from a single query within Recorded Queries", + "stage": "GA", + "codeowner": "@grafana/observability-metrics" + } + }, + { + "metadata": { + "name": "awsDatasourcesTempCredentials", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Support temporary security credentials in AWS plugins for Grafana Cloud customers", + "stage": "experimental", + "codeowner": "@grafana/aws-datasources" + } + }, + { + "metadata": { + "name": "addFieldFromCalculationStatFunctions", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Add cumulative and window functions to the add field from calculation transformation", + "stage": "preview", + "codeowner": "@grafana/dataviz-squad", + "frontend": true + } + }, + { + "metadata": { + "name": "regressionTransformation", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables regression analysis transformation", + "stage": "preview", + "codeowner": "@grafana/dataviz-squad", + "frontend": true + } + }, + { + "metadata": { + "name": "autoMigrateStatPanel", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Migrate old stat panel to supported stat panel - broken out from autoMigrateOldPanels to enable granular tracking", + "stage": "preview", + "codeowner": "@grafana/dataviz-squad", + "frontend": true + } + }, + { + "metadata": { + "name": "grpcServer", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Run the GRPC server", + "stage": "preview", + "codeowner": "@grafana/grafana-app-platform-squad", + "hideFromAdminPage": true + } + }, + { + "metadata": { + "name": "enableElasticsearchBackendQuerying", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enable the processing of queries and responses in the Elasticsearch data source through backend", + "stage": "GA", + "codeowner": "@grafana/observability-logs", + "allowSelfServe": true + } + }, + { + "metadata": { + "name": "enableDatagridEditing", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables the edit functionality in the datagrid panel", + "stage": "preview", + "codeowner": "@grafana/dataviz-squad", + "frontend": true + } + }, + { + "metadata": { + "name": "lokiFormatQuery", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables the ability to format Loki queries", + "stage": "experimental", + "codeowner": "@grafana/observability-logs", + "frontend": true + } + }, + { + "metadata": { + "name": "influxdbSqlSupport", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enable InfluxDB SQL query language support with new querying UI", + "stage": "GA", + "codeowner": "@grafana/observability-metrics", + "requiresRestart": true, + "allowSelfServe": true + } + }, + { + "metadata": { + "name": "alertingNoDataErrorExecution", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Changes how Alerting state manager handles execution of NoData/Error", + "stage": "GA", + "codeowner": "@grafana/alerting-squad", + "requiresRestart": true + } + }, + { + "metadata": { + "name": "pluginsAPIMetrics", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Sends metrics of public grafana packages usage by plugins", + "stage": "experimental", + "codeowner": "@grafana/plugins-platform-backend", + "frontend": true + } + }, + { + "metadata": { + "name": "correlations", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Correlations page", + "stage": "GA", + "codeowner": "@grafana/explore-squad", + "allowSelfServe": true + } + }, + { + "metadata": { + "name": "showDashboardValidationWarnings", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Show warnings when dashboards do not validate against the schema", + "stage": "experimental", + "codeowner": "@grafana/dashboards-squad" + } + }, + { + "metadata": { + "name": "canvasPanelPanZoom", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Allow pan and zoom in canvas panel", + "stage": "preview", + "codeowner": "@grafana/dataviz-squad", + "frontend": true + } + }, + { + "metadata": { + "name": "oauthRequireSubClaim", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Require that sub claims is present in oauth tokens.", + "stage": "experimental", + "codeowner": "@grafana/identity-access-team", + "hideFromAdminPage": true, + "hideFromDocs": true + } + }, + { + "metadata": { + "name": "disableEnvelopeEncryption", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Disable envelope encryption (emergency only)", + "stage": "GA", + "codeowner": "@grafana/grafana-as-code", + "hideFromAdminPage": true + } + }, + { + "metadata": { + "name": "datasourceQueryMultiStatus", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Introduce HTTP 207 Multi Status for api/ds/query", + "stage": "experimental", + "codeowner": "@grafana/plugins-platform-backend" + } + }, + { + "metadata": { + "name": "formatString", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enable format string transformer", + "stage": "preview", + "codeowner": "@grafana/dataviz-squad", + "frontend": true + } + }, + { + "metadata": { + "name": "alertingNoNormalState", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Stop maintaining state of alerts that are not firing", + "stage": "preview", + "codeowner": "@grafana/alerting-squad", + "hideFromAdminPage": true + } + }, + { + "metadata": { + "name": "pluginsDynamicAngularDetectionPatterns", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables fetching Angular detection patterns for plugins from GCOM and fallback to hardcoded ones", + "stage": "GA", + "codeowner": "@grafana/plugins-platform-backend" + } + }, + { + "metadata": { + "name": "dashboardEmbed", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Allow embedding dashboard for external use in Code editors", + "stage": "experimental", + "codeowner": "@grafana/grafana-as-code", + "frontend": true + } + }, + { + "metadata": { + "name": "lokiRunQueriesInParallel", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables running Loki queries in parallel", + "stage": "privatePreview", + "codeowner": "@grafana/observability-logs" + } + }, + { + "metadata": { + "name": "idForwarding", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Generate signed id token for identity that can be forwarded to plugins and external services", + "stage": "experimental", + "codeowner": "@grafana/identity-access-team" + } + }, + { + "metadata": { + "name": "panelMonitoring", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables panel monitoring through logs and measurements", + "stage": "GA", + "codeowner": "@grafana/dataviz-squad", + "frontend": true + } + }, + { + "metadata": { + "name": "flameGraphItemCollapsing", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Allow collapsing of flame graph items", + "stage": "experimental", + "codeowner": "@grafana/observability-traces-and-profiling", + "frontend": true + } + }, + { + "metadata": { + "name": "newFolderPicker", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables the nested folder picker without having nested folders enabled", + "stage": "experimental", + "codeowner": "@grafana/grafana-frontend-platform", + "frontend": true + } + }, + { + "metadata": { + "name": "exploreContentOutline", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Content outline sidebar", + "stage": "GA", + "codeowner": "@grafana/explore-squad", + "frontend": true, + "allowSelfServe": true + } + }, + { + "metadata": { + "name": "nestedFolderPicker", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables the new folder picker to work with nested folders. Requires the nestedFolders feature toggle", + "stage": "GA", + "codeowner": "@grafana/grafana-frontend-platform", + "frontend": true, + "allowSelfServe": true + } + }, + { + "metadata": { + "name": "expressionParser", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enable new expression parser", + "stage": "experimental", + "codeowner": "@grafana/grafana-app-platform-squad", + "requiresRestart": true + } + }, + { + "metadata": { + "name": "alertmanagerRemoteOnly", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Disable the internal Alertmanager and only use the external one defined.", + "stage": "experimental", + "codeowner": "@grafana/alerting-squad" + } + }, + { + "metadata": { + "name": "dashboardSceneForViewers", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables dashboard rendering using Scenes for viewer roles", + "stage": "experimental", + "codeowner": "@grafana/dashboards-squad", + "frontend": true + } + }, + { + "metadata": { + "name": "topnav", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables topnav support in external plugins. The new Grafana navigation cannot be disabled.", + "stage": "deprecated", + "codeowner": "@grafana/grafana-frontend-platform" + } + }, + { + "metadata": { + "name": "lokiLogsDataplane", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Changes logs responses from Loki to be compliant with the dataplane specification.", + "stage": "experimental", + "codeowner": "@grafana/observability-logs" + } + }, + { + "metadata": { + "name": "mlExpressions", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enable support for Machine Learning in server-side expressions", + "stage": "experimental", + "codeowner": "@grafana/alerting-squad" + } + }, + { + "metadata": { + "name": "kubernetesPlaylists", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s", + "stage": "experimental", + "codeowner": "@grafana/grafana-app-platform-squad", + "requiresRestart": true + } + }, + { + "metadata": { + "name": "alertmanagerRemoteSecondary", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enable Grafana to sync configuration and state with a remote Alertmanager.", + "stage": "experimental", + "codeowner": "@grafana/alerting-squad" + } + }, + { + "metadata": { + "name": "influxdbBackendMigration", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Query InfluxDB InfluxQL without the proxy", + "stage": "GA", + "codeowner": "@grafana/observability-metrics", + "frontend": true + } + }, + { + "metadata": { + "name": "pluginsFrontendSandbox", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables the plugins frontend sandbox", + "stage": "experimental", + "codeowner": "@grafana/plugins-platform-backend", + "frontend": true + } + }, + { + "metadata": { + "name": "editPanelCSVDragAndDrop", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables drag and drop for CSV and Excel files", + "stage": "experimental", + "codeowner": "@grafana/dataviz-squad", + "frontend": true + } + }, + { + "metadata": { + "name": "prometheusMetricEncyclopedia", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Adds the metrics explorer component to the Prometheus query builder as an option in metric select", + "stage": "GA", + "codeowner": "@grafana/observability-metrics", + "frontend": true, + "allowSelfServe": true + } + }, + { + "metadata": { + "name": "dataplaneFrontendFallback", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Support dataplane contract field name change for transformations and field name matchers where the name is different", + "stage": "GA", + "codeowner": "@grafana/observability-metrics", + "frontend": true, + "allowSelfServe": true + } + }, + { + "metadata": { + "name": "faroDatasourceSelector", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enable the data source selector within the Frontend Apps section of the Frontend Observability", + "stage": "preview", + "codeowner": "@grafana/app-o11y", + "frontend": true + } + }, + { + "metadata": { + "name": "lokiStructuredMetadata", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables the loki data source to request structured metadata from the Loki server", + "stage": "GA", + "codeowner": "@grafana/observability-logs" + } + }, + { + "metadata": { + "name": "sqlExpressions", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables using SQL and DuckDB functions as Expressions.", + "stage": "experimental", + "codeowner": "@grafana/grafana-app-platform-squad" + } + }, + { + "metadata": { + "name": "live-service-web-worker", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "This will use a webworker thread to processes events rather than the main thread", + "stage": "experimental", + "codeowner": "@grafana/grafana-app-platform-squad", + "frontend": true + } + }, + { + "metadata": { + "name": "disableAngular", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Dynamic flag to disable angular at runtime. The preferred method is to set `angular_support_enabled` to `false` in the [security] settings, which allows you to change the state at runtime.", + "stage": "preview", + "codeowner": "@grafana/dataviz-squad", + "frontend": true, + "hideFromAdminPage": true + } + }, + { + "metadata": { + "name": "usePrometheusFrontendPackage", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Use the @grafana/prometheus frontend package in core Prometheus.", + "stage": "experimental", + "codeowner": "@grafana/observability-metrics", + "frontend": true + } + }, + { + "metadata": { + "name": "alertingBacktesting", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Rule backtesting API for alerting", + "stage": "experimental", + "codeowner": "@grafana/alerting-squad" + } + }, + { + "metadata": { + "name": "prometheusDataplane", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Changes responses to from Prometheus to be compliant with the dataplane specification. In particular, when this feature toggle is active, the numeric `Field.Name` is set from 'Value' to the value of the `__name__` label.", + "stage": "GA", + "codeowner": "@grafana/observability-metrics", + "allowSelfServe": true + } + }, + { + "metadata": { + "name": "lokiPredefinedOperations", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Adds predefined query operations to Loki query editor", + "stage": "experimental", + "codeowner": "@grafana/observability-logs", + "frontend": true + } + }, + { + "metadata": { + "name": "autoMigrateTablePanel", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Migrate old table panel to supported table panel - broken out from autoMigrateOldPanels to enable granular tracking", + "stage": "preview", + "codeowner": "@grafana/dataviz-squad", + "frontend": true + } + }, + { + "metadata": { + "name": "nestedFolders", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enable folder nesting", + "stage": "GA", "codeowner": "@grafana/backend-platform" } }, + { + "metadata": { + "name": "sqlDatasourceDatabaseSelection", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables previous SQL data source dataset dropdown behavior", + "stage": "preview", + "codeowner": "@grafana/dataviz-squad", + "frontend": true, + "hideFromAdminPage": true + } + }, + { + "metadata": { + "name": "logsExploreTableVisualisation", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "A table visualisation for logs in Explore", + "stage": "GA", + "codeowner": "@grafana/observability-logs", + "frontend": true + } + }, + { + "metadata": { + "name": "logRowsPopoverMenu", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enable filtering menu displayed when text of a log line is selected", + "stage": "GA", + "codeowner": "@grafana/observability-logs", + "frontend": true + } + }, + { + "metadata": { + "name": "alertingQueryOptimization", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Optimizes eligible queries in order to reduce load on datasources", + "stage": "GA", + "codeowner": "@grafana/alerting-squad" + } + }, + { + "metadata": { + "name": "promQLScope", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "In-development feature that will allow injection of labels into prometheus queries.", + "stage": "experimental", + "codeowner": "@grafana/observability-metrics" + } + }, + { + "metadata": { + "name": "nodeGraphDotLayout", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Changed the layout algorithm for the node graph", + "stage": "experimental", + "codeowner": "@grafana/observability-traces-and-profiling", + "frontend": true + } + }, { "metadata": { "name": "disableSecretsCompatibility", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { "description": "Disable duplicated secret storage in legacy tables", @@ -2065,28 +1629,463 @@ }, { "metadata": { - "name": "alertmanagerRemotePrimary", - "resourceVersion": "1712260369192", - "creationTimestamp": "2024-04-04T19:52:49Z" + "name": "logRequestsInstrumentedAsUnknown", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { - "description": "Enable Grafana to have a remote Alertmanager instance as the primary Alertmanager.", + "description": "Logs the path for requests that are instrumented as unknown", + "stage": "experimental", + "codeowner": "@grafana/hosted-grafana-team" + } + }, + { + "metadata": { + "name": "metricsSummary", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables metrics summary queries in the Tempo data source", + "stage": "experimental", + "codeowner": "@grafana/observability-traces-and-profiling", + "frontend": true + } + }, + { + "metadata": { + "name": "renderAuthJWT", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Uses JWT-based auth for rendering instead of relying on remote cache", + "stage": "preview", + "codeowner": "@grafana/grafana-as-code", + "hideFromAdminPage": true + } + }, + { + "metadata": { + "name": "refactorVariablesTimeRange", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Refactor time range variables flow to reduce number of API calls made when query variables are chained", + "stage": "preview", + "codeowner": "@grafana/dashboards-squad", + "hideFromAdminPage": true + } + }, + { + "metadata": { + "name": "annotationPermissionUpdate", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Change the way annotation permissions work by scoping them to folders and dashboards.", + "stage": "GA", + "codeowner": "@grafana/identity-access-team" + } + }, + { + "metadata": { + "name": "kubernetesFeatureToggles", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Use the kubernetes API for feature toggle management in the frontend", + "stage": "experimental", + "codeowner": "@grafana/grafana-operator-experience-squad", + "frontend": true, + "hideFromAdminPage": true + } + }, + { + "metadata": { + "name": "autoMigrateGraphPanel", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Migrate old graph panel to supported time series panel - broken out from autoMigrateOldPanels to enable granular tracking", + "stage": "preview", + "codeowner": "@grafana/dataviz-squad", + "frontend": true + } + }, + { + "metadata": { + "name": "frontendSandboxMonitorOnly", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables monitor only in the plugin frontend sandbox (if enabled)", + "stage": "experimental", + "codeowner": "@grafana/plugins-platform-backend", + "frontend": true + } + }, + { + "metadata": { + "name": "alertStateHistoryLokiOnly", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Disable Grafana alerts from emitting annotations when a remote Loki instance is available.", "stage": "experimental", "codeowner": "@grafana/alerting-squad" } }, { "metadata": { - "name": "prometheusCodeModeMetricNamesSearch", - "resourceVersion": "1712241392690", - "creationTimestamp": "2024-04-04T14:36:32Z" + "name": "dashboardScene", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" }, "spec": { - "description": "Enables search for metric names in Code Mode, to improve performance when working with an enormous number of metric names", + "description": "Enables dashboard rendering using scenes for all roles", "stage": "experimental", - "codeowner": "@grafana/observability-metrics", + "codeowner": "@grafana/dashboards-squad", "frontend": true } + }, + { + "metadata": { + "name": "newPDFRendering", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "New implementation for the dashboard-to-PDF rendering", + "stage": "preview", + "codeowner": "@grafana/sharing-squad" + } + }, + { + "metadata": { + "name": "queryOverLive", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Use Grafana Live WebSocket to execute backend queries", + "stage": "experimental", + "codeowner": "@grafana/grafana-app-platform-squad", + "frontend": true + } + }, + { + "metadata": { + "name": "dashboardSceneSolo", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables rendering dashboards using scenes for solo panels", + "stage": "experimental", + "codeowner": "@grafana/dashboards-squad", + "frontend": true + } + }, + { + "metadata": { + "name": "permissionsFilterRemoveSubquery", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Alternative permission filter implementation that does not use subqueries for fetching the dashboard folder", + "stage": "experimental", + "codeowner": "@grafana/backend-platform" + } + }, + { + "metadata": { + "name": "enableNativeHTTPHistogram", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables native HTTP Histograms", + "stage": "experimental", + "codeowner": "@grafana/hosted-grafana-team" + } + }, + { + "metadata": { + "name": "jitterAlertRulesWithinGroups", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Distributes alert rule evaluations more evenly over time, including spreading out rules within the same group", + "stage": "preview", + "codeowner": "@grafana/alerting-squad", + "requiresRestart": true, + "hideFromDocs": true + } + }, + { + "metadata": { + "name": "influxqlStreamingParser", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enable streaming JSON parser for InfluxDB datasource InfluxQL query language", + "stage": "experimental", + "codeowner": "@grafana/observability-metrics" + } + }, + { + "metadata": { + "name": "alertStateHistoryLokiSecondary", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enable Grafana to write alert state history to an external Loki instance in addition to Grafana annotations.", + "stage": "experimental", + "codeowner": "@grafana/alerting-squad" + } + }, + { + "metadata": { + "name": "logsContextDatasourceUi", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Allow datasource to provide custom UI for context view", + "stage": "GA", + "codeowner": "@grafana/observability-logs", + "frontend": true, + "allowSelfServe": true + } + }, + { + "metadata": { + "name": "traceQLStreaming", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables response streaming of TraceQL queries of the Tempo data source", + "stage": "GA", + "codeowner": "@grafana/observability-traces-and-profiling", + "frontend": true + } + }, + { + "metadata": { + "name": "wargamesTesting", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Placeholder feature flag for internal testing", + "stage": "experimental", + "codeowner": "@grafana/hosted-grafana-team" + } + }, + { + "metadata": { + "name": "externalServiceAccounts", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Automatic service account and token setup for plugins", + "stage": "preview", + "codeowner": "@grafana/identity-access-team", + "hideFromAdminPage": true + } + }, + { + "metadata": { + "name": "publicDashboardsEmailSharing", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables public dashboard sharing to be restricted to only allowed emails", + "stage": "preview", + "codeowner": "@grafana/sharing-squad", + "hideFromAdminPage": true, + "hideFromDocs": true + } + }, + { + "metadata": { + "name": "storage", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Configurable storage for dashboards, datasources, and resources", + "stage": "experimental", + "codeowner": "@grafana/grafana-app-platform-squad" + } + }, + { + "metadata": { + "name": "individualCookiePreferences", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Support overriding cookie preferences per user", + "stage": "experimental", + "codeowner": "@grafana/backend-platform" + } + }, + { + "metadata": { + "name": "sseGroupByDatasource", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Send query to the same datasource in a single request when using server side expressions. The `cloudWatchBatchQueries` feature toggle should be enabled if this used with CloudWatch.", + "stage": "experimental", + "codeowner": "@grafana/observability-metrics" + } + }, + { + "metadata": { + "name": "managedPluginsInstall", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Install managed plugins directly from plugins catalog", + "stage": "GA", + "codeowner": "@grafana/plugins-platform-backend" + } + }, + { + "metadata": { + "name": "ssoSettingsSAML", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Use the new SSO Settings API to configure the SAML connector", + "stage": "experimental", + "codeowner": "@grafana/identity-access-team", + "hideFromAdminPage": true, + "hideFromDocs": true + } + }, + { + "metadata": { + "name": "lokiExperimentalStreaming", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Support new streaming approach for loki (prototype, needs special loki build)", + "stage": "experimental", + "codeowner": "@grafana/observability-logs" + } + }, + { + "metadata": { + "name": "accessControlOnCall", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Access control primitives for OnCall", + "stage": "preview", + "codeowner": "@grafana/identity-access-team", + "hideFromAdminPage": true + } + }, + { + "metadata": { + "name": "dashgpt", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enable AI powered features in dashboards", + "stage": "GA", + "codeowner": "@grafana/dashboards-squad", + "frontend": true + } + }, + { + "metadata": { + "name": "awsDatasourcesNewFormStyling", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Applies new form styling for configuration and query editors in AWS plugins", + "stage": "preview", + "codeowner": "@grafana/aws-datasources", + "frontend": true + } + }, + { + "metadata": { + "name": "exploreMetrics", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables the new Explore Metrics core app", + "stage": "GA", + "codeowner": "@grafana/dashboards-squad", + "frontend": true + } + }, + { + "metadata": { + "name": "lokiQueryHints", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables query hints for Loki", + "stage": "GA", + "codeowner": "@grafana/observability-logs", + "frontend": true + } + }, + { + "metadata": { + "name": "scenes", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Experimental framework to build interactive dashboards", + "stage": "experimental", + "codeowner": "@grafana/dashboards-squad", + "frontend": true + } + }, + { + "metadata": { + "name": "transformationsRedesign", + "resourceVersion": "1712639261786", + "creationTimestamp": "2024-04-09T05:07:41Z" + }, + "spec": { + "description": "Enables the transformations redesign", + "stage": "GA", + "codeowner": "@grafana/observability-metrics", + "frontend": true, + "allowSelfServe": true + } } ] } \ No newline at end of file diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go index 3b241ca8957..b2bdbb30b22 100644 --- a/pkg/services/navtree/navtreeimpl/navtree.go +++ b/pkg/services/navtree/navtreeimpl/navtree.go @@ -465,7 +465,7 @@ func (s *ServiceImpl) buildDataConnectionsNavLink(c *contextmodel.ReqContext) *n func (s *ServiceImpl) buildExploreNavLinks(c *contextmodel.ReqContext) []*navtree.NavLink { exploreChildNavs := []*navtree.NavLink{} - if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagDatatrails) { + if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagExploreMetrics) { exploreChildNavs = append(exploreChildNavs, &navtree.NavLink{ Text: "Metrics", SubTitle: "Queryless exploration of your metrics", diff --git a/pkg/services/ngalert/api/api_alertmanager.go b/pkg/services/ngalert/api/api_alertmanager.go index 3bc763a08df..bdb3756de43 100644 --- a/pkg/services/ngalert/api/api_alertmanager.go +++ b/pkg/services/ngalert/api/api_alertmanager.go @@ -65,11 +65,6 @@ func (srv AlertmanagerSrv) RouteCreateSilence(c *contextmodel.ReqContext, postab return ErrResp(http.StatusBadRequest, err, "silence failed validation") } - am, errResp := srv.AlertmanagerFor(c.SignedInUser.GetOrgID()) - if errResp != nil { - return errResp - } - action := accesscontrol.ActionAlertingInstanceUpdate if postableSilence.ID == "" { action = accesscontrol.ActionAlertingInstanceCreate @@ -83,8 +78,15 @@ func (srv AlertmanagerSrv) RouteCreateSilence(c *contextmodel.ReqContext, postab return response.Err(authz.NewAuthorizationErrorWithPermissions(fmt.Sprintf("%s silences", errAction), evaluator)) } - silenceID, err := am.CreateSilence(c.Req.Context(), &postableSilence) + silenceID, err := srv.mam.CreateSilence(c.Req.Context(), c.SignedInUser.GetOrgID(), &postableSilence) if err != nil { + if errors.Is(err, notifier.ErrNoAlertmanagerForOrg) { + return ErrResp(http.StatusNotFound, err, "") + } + if errors.Is(err, notifier.ErrAlertmanagerNotReady) { + return ErrResp(http.StatusConflict, err, "") + } + if errors.Is(err, alertingNotify.ErrSilenceNotFound) { return ErrResp(http.StatusNotFound, err, "") } @@ -115,12 +117,13 @@ func (srv AlertmanagerSrv) RouteDeleteAlertingConfig(c *contextmodel.ReqContext) } func (srv AlertmanagerSrv) RouteDeleteSilence(c *contextmodel.ReqContext, silenceID string) response.Response { - am, errResp := srv.AlertmanagerFor(c.SignedInUser.GetOrgID()) - if errResp != nil { - return errResp - } - - if err := am.DeleteSilence(c.Req.Context(), silenceID); err != nil { + if err := srv.mam.DeleteSilence(c.Req.Context(), c.SignedInUser.GetOrgID(), silenceID); err != nil { + if errors.Is(err, notifier.ErrNoAlertmanagerForOrg) { + return ErrResp(http.StatusNotFound, err, "") + } + if errors.Is(err, notifier.ErrAlertmanagerNotReady) { + return ErrResp(http.StatusConflict, err, "") + } if errors.Is(err, alertingNotify.ErrSilenceNotFound) { return ErrResp(http.StatusNotFound, err, "") } diff --git a/pkg/services/ngalert/notifier/alertmanager.go b/pkg/services/ngalert/notifier/alertmanager.go index 3eaf3073754..3c4cc7101b0 100644 --- a/pkg/services/ngalert/notifier/alertmanager.go +++ b/pkg/services/ngalert/notifier/alertmanager.go @@ -417,6 +417,11 @@ func (am *alertmanager) PutAlerts(_ context.Context, postableAlerts apimodels.Po return am.Base.PutAlerts(alerts) } +// SilenceState returns the current internal state of silences. +func (am *alertmanager) SilenceState(_ context.Context) (alertingNotify.SilenceState, error) { + return am.Base.SilenceState() +} + // AlertValidationError is the error capturing the validation errors // faced on the alerts. type AlertValidationError struct { diff --git a/pkg/services/ngalert/notifier/alertmanager_mock/Alertmanager.go b/pkg/services/ngalert/notifier/alertmanager_mock/Alertmanager.go index 243297b297d..ce0916ef963 100644 --- a/pkg/services/ngalert/notifier/alertmanager_mock/Alertmanager.go +++ b/pkg/services/ngalert/notifier/alertmanager_mock/Alertmanager.go @@ -711,6 +711,60 @@ func (_c *AlertmanagerMock_SaveAndApplyDefaultConfig_Call) RunAndReturn(run func return _c } +// SilenceState provides a mock function with given fields: _a0 +func (_m *AlertmanagerMock) SilenceState(_a0 context.Context) (notify.SilenceState, error) { + ret := _m.Called(_a0) + + var r0 notify.SilenceState + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (notify.SilenceState, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(context.Context) notify.SilenceState); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(notify.SilenceState) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// AlertmanagerMock_SilenceState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SilenceState' +type AlertmanagerMock_SilenceState_Call struct { + *mock.Call +} + +// SilenceState is a helper method to define mock.On call +// - _a0 context.Context +func (_e *AlertmanagerMock_Expecter) SilenceState(_a0 interface{}) *AlertmanagerMock_SilenceState_Call { + return &AlertmanagerMock_SilenceState_Call{Call: _e.mock.On("SilenceState", _a0)} +} + +func (_c *AlertmanagerMock_SilenceState_Call) Run(run func(_a0 context.Context)) *AlertmanagerMock_SilenceState_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *AlertmanagerMock_SilenceState_Call) Return(_a0 notify.SilenceState, _a1 error) *AlertmanagerMock_SilenceState_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *AlertmanagerMock_SilenceState_Call) RunAndReturn(run func(context.Context) (notify.SilenceState, error)) *AlertmanagerMock_SilenceState_Call { + _c.Call.Return(run) + return _c +} + // StopAndWait provides a mock function with given fields: func (_m *AlertmanagerMock) StopAndWait() { _m.Called() diff --git a/pkg/services/ngalert/notifier/multiorg_alertmanager.go b/pkg/services/ngalert/notifier/multiorg_alertmanager.go index 071fbd7cbe0..120fb5e960d 100644 --- a/pkg/services/ngalert/notifier/multiorg_alertmanager.go +++ b/pkg/services/ngalert/notifier/multiorg_alertmanager.go @@ -6,9 +6,10 @@ import ( "sync" "time" - alertingCluster "github.com/grafana/alerting/cluster" "github.com/prometheus/client_golang/prometheus" + alertingCluster "github.com/grafana/alerting/cluster" + alertingNotify "github.com/grafana/alerting/notify" "github.com/grafana/grafana/pkg/infra/kvstore" @@ -42,6 +43,10 @@ type Alertmanager interface { GetSilence(context.Context, string) (apimodels.GettableSilence, error) ListSilences(context.Context, []string) (apimodels.GettableSilences, error) + // SilenceState returns the current state of silences in the Alertmanager. This is used to persist the state + // to the kvstore. + SilenceState(context.Context) (alertingNotify.SilenceState, error) + // Alerts GetAlerts(ctx context.Context, active, silenced, inhibited bool, filter []string, receiver string) (apimodels.GettableAlerts, error) GetAlertGroups(ctx context.Context, active, silenced, inhibited bool, filter []string, receiver string) (apimodels.AlertGroups, error) @@ -395,6 +400,83 @@ func (moa *MultiOrgAlertmanager) AlertmanagerFor(orgID int64) (Alertmanager, err return orgAM, nil } +// CreateSilence creates a silence in the Alertmanager for the organization provided, returning the silence ID. It will +// also persist the silence state to the kvstore immediately after creating the silence. +func (moa *MultiOrgAlertmanager) CreateSilence(ctx context.Context, orgID int64, ps *alertingNotify.PostableSilence) (string, error) { + moa.alertmanagersMtx.RLock() + defer moa.alertmanagersMtx.RUnlock() + + orgAM, existing := moa.alertmanagers[orgID] + if !existing { + return "", ErrNoAlertmanagerForOrg + } + + if !orgAM.Ready() { + return "", ErrAlertmanagerNotReady + } + + // Need to create the silence in the AM first to get the silence ID. + silenceID, err := orgAM.CreateSilence(ctx, ps) + if err != nil { + return "", err + } + + err = moa.updateSilenceState(ctx, orgAM, orgID) + if err != nil { + moa.logger.Warn("Failed to persist silence state on create, will be corrected by next maintenance run", "orgID", orgID, "silenceID", silenceID, "error", err) + } + + return silenceID, nil +} + +// DeleteSilence deletes a silence in the Alertmanager for the organization provided. It will also persist the silence +// state to the kvstore immediately after deleting the silence. +func (moa *MultiOrgAlertmanager) DeleteSilence(ctx context.Context, orgID int64, silenceID string) error { + moa.alertmanagersMtx.RLock() + defer moa.alertmanagersMtx.RUnlock() + + orgAM, existing := moa.alertmanagers[orgID] + if !existing { + return ErrNoAlertmanagerForOrg + } + + if !orgAM.Ready() { + return ErrAlertmanagerNotReady + } + + err := orgAM.DeleteSilence(ctx, silenceID) + if err != nil { + return err + } + + err = moa.updateSilenceState(ctx, orgAM, orgID) + if err != nil { + moa.logger.Warn("Failed to persist silence state on delete, will be corrected by next maintenance run", "orgID", orgID, "silenceID", silenceID, "error", err) + } + + return nil +} + +// updateSilenceState persists the silence state to the kvstore immediately instead of waiting for the next maintenance +// run. This is used after Create/Delete to prevent silences from being lost when a new Alertmanager is started before +// the state has persisted. This can happen, for example, in a rolling deployment scenario. +func (moa *MultiOrgAlertmanager) updateSilenceState(ctx context.Context, orgAM Alertmanager, orgID int64) error { + // Collect the internal silence state from the AM. + // TODO: Currently, we rely on the AM itself for the persisted silence state representation. Preferably, we would + // define the state ourselves and persist it in a format that is easy to guarantee consistency for writes to + // individual silences. In addition to the consistency benefits, this would also allow us to avoid the need for + // a network request to the AM to get the state in the case of remote alertmanagers. + silences, err := orgAM.SilenceState(ctx) + if err != nil { + return err + } + + // Persist to kvstore. + fs := NewFileStore(orgID, moa.kvStore) + _, err = fs.SaveSilences(ctx, silences) + return err +} + // NilPeer and NilChannel implements the Alertmanager clustering interface. type NilPeer struct{} diff --git a/pkg/services/ngalert/notifier/multiorg_alertmanager_remote_test.go b/pkg/services/ngalert/notifier/multiorg_alertmanager_remote_test.go index 4e9196e6594..47deb66790a 100644 --- a/pkg/services/ngalert/notifier/multiorg_alertmanager_remote_test.go +++ b/pkg/services/ngalert/notifier/multiorg_alertmanager_remote_test.go @@ -126,8 +126,8 @@ func TestMultiorgAlertmanager_RemoteSecondaryMode(t *testing.T) { OrgID: 1, LastApplied: time.Now().Unix(), })) - require.NoError(t, kvStore.Set(ctx, 1, "alertmanager", notifier.SilencesFilename, "dGVzdAo=")) // base64-encoded string "test" - require.NoError(t, kvStore.Set(ctx, 1, "alertmanager", notifier.NotificationLogFilename, "dGVzdAo=")) // base64-encoded string "test" + require.NoError(t, kvStore.Set(ctx, 1, "alertmanager", notifier.SilencesFilename, "lwEKhgEKATISFxIJYWxlcnRuYW1lGgp0ZXN0X2FsZXJ0EiMSDmdyYWZhbmFfZm9sZGVyGhF0ZXN0X2FsZXJ0X2ZvbGRlchoMCN2CkbAGEJbKrMsDIgwI7Z6RsAYQlsqsywMqCwiAkrjDmP7///8BQgxHcmFmYW5hIFRlc3RKDFRlc3QgU2lsZW5jZRIMCO2ekbAGEJbKrMsDlwEKhgEKATESFxIJYWxlcnRuYW1lGgp0ZXN0X2FsZXJ0EiMSDmdyYWZhbmFfZm9sZGVyGhF0ZXN0X2FsZXJ0X2ZvbGRlchoMCN2CkbAGEJbKrMsDIgwI7Z6RsAYQlsqsywMqCwiAkrjDmP7///8BQgxHcmFmYW5hIFRlc3RKDFRlc3QgU2lsZW5jZRIMCO2ekbAGEJbKrMsD")) + require.NoError(t, kvStore.Set(ctx, 1, "alertmanager", notifier.NotificationLogFilename, "OgoqCgZncm91cDISEgoJcmVjZWl2ZXIyEgV0ZXN0MyoMCLSDkbAGEMvaofYCEgwIxJ+RsAYQy9qh9gI6CioKBmdyb3VwMRISCglyZWNlaXZlcjESBXRlc3QzKgwItIORsAYQy9qh9gISDAjEn5GwBhDL2qH2Ag==")) // The sync interval (10s) has not elapsed yet, syncing should have no effect. require.NoError(t, moa.LoadAndSyncAlertmanagersForOrgs(ctx)) @@ -151,8 +151,8 @@ func TestMultiorgAlertmanager_RemoteSecondaryMode(t *testing.T) { OrgID: 1, LastApplied: time.Now().Unix(), })) - require.NoError(t, kvStore.Set(ctx, 1, "alertmanager", notifier.SilencesFilename, "dGVzdC0yCg==")) // base64-encoded string "test-2" - require.NoError(t, kvStore.Set(ctx, 1, "alertmanager", notifier.NotificationLogFilename, "dGVzdC0yCg==")) // base64-encoded string "test-2" + require.NoError(t, kvStore.Set(ctx, 1, "alertmanager", notifier.SilencesFilename, "lwEKhgEKAWESFxIJYWxlcnRuYW1lGgp0ZXN0X2FsZXJ0EiMSDmdyYWZhbmFfZm9sZGVyGhF0ZXN0X2FsZXJ0X2ZvbGRlchoMCPuEkbAGEK3AhM8CIgwIi6GRsAYQrcCEzwIqCwiAkrjDmP7///8BQgxHcmFmYW5hIFRlc3RKDFRlc3QgU2lsZW5jZRIMCIuhkbAGEK3AhM8ClwEKhgEKAWISFxIJYWxlcnRuYW1lGgp0ZXN0X2FsZXJ0EiMSDmdyYWZhbmFfZm9sZGVyGhF0ZXN0X2FsZXJ0X2ZvbGRlchoMCPuEkbAGEK3AhM8CIgwIi6GRsAYQrcCEzwIqCwiAkrjDmP7///8BQgxHcmFmYW5hIFRlc3RKDFRlc3QgU2lsZW5jZRIMCIuhkbAGEK3AhM8C")) + require.NoError(t, kvStore.Set(ctx, 1, "alertmanager", notifier.NotificationLogFilename, "OAopCgZncm91cEESEgoJcmVjZWl2ZXJBEgV0ZXN0MyoLCNmEkbAGEOzO0BUSCwjpoJGwBhDsztAVOAopCgZncm91cEISEgoJcmVjZWl2ZXJCEgV0ZXN0MyoLCNmEkbAGEOzO0BUSCwjpoJGwBhDsztAV")) // Both state and config should be updated when shutting the Alertmanager down. moa.StopAndWait() diff --git a/pkg/services/ngalert/notifier/multiorg_alertmanager_test.go b/pkg/services/ngalert/notifier/multiorg_alertmanager_test.go index d053210eb3a..13c142f47f4 100644 --- a/pkg/services/ngalert/notifier/multiorg_alertmanager_test.go +++ b/pkg/services/ngalert/notifier/multiorg_alertmanager_test.go @@ -6,6 +6,8 @@ import ( "testing" "time" + alertingNotify "github.com/grafana/alerting/notify" + "github.com/prometheus/alertmanager/types" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/require" @@ -22,18 +24,7 @@ import ( ) func TestMultiOrgAlertmanager_SyncAlertmanagersForOrgs(t *testing.T) { - configStore := NewFakeConfigStore(t, map[int64]*models.AlertConfiguration{}) - orgStore := &FakeOrgStore{ - orgs: []int64{1, 2, 3}, - } - tmpDir := t.TempDir() - kvStore := ngfakes.NewFakeKVStore(t) - provStore := ngfakes.NewFakeProvisioningStore() - secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore()) - decryptFn := secretsService.GetDecryptedValue - reg := prometheus.NewPedanticRegistry() - m := metrics.NewNGAlert(reg) cfg := &setting.Cfg{ DataPath: tmpDir, UnifiedAlerting: setting.UnifiedAlertingSettings{ @@ -42,8 +33,9 @@ func TestMultiOrgAlertmanager_SyncAlertmanagersForOrgs(t *testing.T) { DisabledOrgs: map[int64]struct{}{5: {}}, }, // do not poll in tests. } - mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, provStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger"), secretsService, featuremgmt.WithFeatures()) - require.NoError(t, err) + mam := setupMam(t, cfg) + reg := mam.metrics.Registerer.(*prometheus.Registry) + orgStore := mam.orgStore.(*FakeOrgStore) ctx := context.Background() // Ensure that one Alertmanager is created per org. @@ -61,7 +53,7 @@ grafana_alerting_discovered_configurations 3 // Configurations should be marked as successfully applied. for _, org := range orgStore.orgs { - configs, err := configStore.GetAppliedConfigurations(ctx, org, 10) + configs, err := mam.configStore.GetAppliedConfigurations(ctx, org, 10) require.NoError(t, err) require.Len(t, configs, 1) } @@ -106,58 +98,40 @@ grafana_alerting_discovered_configurations 4 orgID := int64(6) // Populate the kvstore with orphaned records. - err = kvStore.Set(ctx, orgID, KVNamespace, SilencesFilename, "file_1") + err := mam.kvStore.Set(ctx, orgID, KVNamespace, SilencesFilename, "file_1") require.NoError(t, err) - err = kvStore.Set(ctx, orgID, KVNamespace, NotificationLogFilename, "file_1") + err = mam.kvStore.Set(ctx, orgID, KVNamespace, NotificationLogFilename, "file_1") require.NoError(t, err) // Now re run the sync job once. require.NoError(t, mam.LoadAndSyncAlertmanagersForOrgs(ctx)) // The organization kvstore records should be gone by now. - _, exists, _ := kvStore.Get(ctx, orgID, KVNamespace, SilencesFilename) + _, exists, _ := mam.kvStore.Get(ctx, orgID, KVNamespace, SilencesFilename) require.False(t, exists) - _, exists, _ = kvStore.Get(ctx, orgID, KVNamespace, NotificationLogFilename) + _, exists, _ = mam.kvStore.Get(ctx, orgID, KVNamespace, NotificationLogFilename) require.False(t, exists) } } func TestMultiOrgAlertmanager_SyncAlertmanagersForOrgsWithFailures(t *testing.T) { + mam := setupMam(t, nil) + ctx := context.Background() + // Include a broken configuration for organization 2. var orgWithBadConfig int64 = 2 - configStore := NewFakeConfigStore(t, map[int64]*models.AlertConfiguration{ + mam.configStore = NewFakeConfigStore(t, map[int64]*models.AlertConfiguration{ 2: {AlertmanagerConfiguration: brokenConfig, OrgID: orgWithBadConfig}, }) - orgs := []int64{1, 2, 3} - orgStore := &FakeOrgStore{ - orgs: orgs, - } - - tmpDir := t.TempDir() - kvStore := ngfakes.NewFakeKVStore(t) - provStore := ngfakes.NewFakeProvisioningStore() - secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore()) - decryptFn := secretsService.GetDecryptedValue - reg := prometheus.NewPedanticRegistry() - m := metrics.NewNGAlert(reg) - cfg := &setting.Cfg{ - DataPath: tmpDir, - UnifiedAlerting: setting.UnifiedAlertingSettings{ - AlertmanagerConfigPollInterval: 10 * time.Minute, - DefaultConfiguration: setting.GetAlertmanagerDefaultConfiguration(), - }, // do not poll in tests. - } - mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, provStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger"), secretsService, featuremgmt.WithFeatures()) + orgs, err := mam.orgStore.GetOrgs(ctx) require.NoError(t, err) - ctx := context.Background() - // No successfully applied configurations should be found at first. { for _, org := range orgs { - configs, err := configStore.GetAppliedConfigurations(ctx, org, 10) + configs, err := mam.configStore.GetAppliedConfigurations(ctx, org, 10) require.NoError(t, err) require.Len(t, configs, 0) } @@ -173,7 +147,7 @@ func TestMultiOrgAlertmanager_SyncAlertmanagersForOrgsWithFailures(t *testing.T) // Configurations should be marked as successfully applied for all orgs except for org 2. for _, org := range orgs { - configs, err := configStore.GetAppliedConfigurations(ctx, org, 10) + configs, err := mam.configStore.GetAppliedConfigurations(ctx, org, 10) require.NoError(t, err) if org == orgWithBadConfig { require.Len(t, configs, 0) @@ -193,7 +167,7 @@ func TestMultiOrgAlertmanager_SyncAlertmanagersForOrgsWithFailures(t *testing.T) // The configuration should still be marked as successfully applied for all orgs except for org 2. for _, org := range orgs { - configs, err := configStore.GetAppliedConfigurations(ctx, org, 10) + configs, err := mam.configStore.GetAppliedConfigurations(ctx, org, 10) require.NoError(t, err) if org == orgWithBadConfig { require.Len(t, configs, 0) @@ -205,7 +179,7 @@ func TestMultiOrgAlertmanager_SyncAlertmanagersForOrgsWithFailures(t *testing.T) // If we fix the configuration, it becomes ready. { - configStore.configs = map[int64]*models.AlertConfiguration{} // It'll apply the default config. + mam.configStore.(*fakeConfigStore).configs = map[int64]*models.AlertConfiguration{} // It'll apply the default config. require.NoError(t, mam.LoadAndSyncAlertmanagersForOrgs(ctx)) require.Len(t, mam.alertmanagers, 3) require.True(t, mam.alertmanagers[1].Ready()) @@ -214,7 +188,7 @@ func TestMultiOrgAlertmanager_SyncAlertmanagersForOrgsWithFailures(t *testing.T) // All configurations should be marked as successfully applied. for _, org := range orgs { - configs, err := configStore.GetAppliedConfigurations(ctx, org, 10) + configs, err := mam.configStore.GetAppliedConfigurations(ctx, org, 10) require.NoError(t, err) require.NotEqual(t, 0, len(configs)) } @@ -222,23 +196,7 @@ func TestMultiOrgAlertmanager_SyncAlertmanagersForOrgsWithFailures(t *testing.T) } func TestMultiOrgAlertmanager_AlertmanagerFor(t *testing.T) { - configStore := NewFakeConfigStore(t, map[int64]*models.AlertConfiguration{}) - orgStore := &FakeOrgStore{ - orgs: []int64{1, 2, 3}, - } - tmpDir := t.TempDir() - cfg := &setting.Cfg{ - DataPath: tmpDir, - UnifiedAlerting: setting.UnifiedAlertingSettings{AlertmanagerConfigPollInterval: 3 * time.Minute, DefaultConfiguration: setting.GetAlertmanagerDefaultConfiguration()}, // do not poll in tests. - } - kvStore := ngfakes.NewFakeKVStore(t) - provStore := ngfakes.NewFakeProvisioningStore() - secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore()) - decryptFn := secretsService.GetDecryptedValue - reg := prometheus.NewPedanticRegistry() - m := metrics.NewNGAlert(reg) - mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, provStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger"), secretsService, featuremgmt.WithFeatures()) - require.NoError(t, err) + mam := setupMam(t, nil) ctx := context.Background() // Ensure that one Alertmanagers is created per org. @@ -264,7 +222,7 @@ func TestMultiOrgAlertmanager_AlertmanagerFor(t *testing.T) { } // Let's now remove the previous queried organization. - orgStore.orgs = []int64{1, 3} + mam.orgStore.(*FakeOrgStore).orgs = []int64{1, 3} require.NoError(t, mam.LoadAndSyncAlertmanagersForOrgs(ctx)) { _, err := mam.AlertmanagerFor(2) @@ -273,24 +231,7 @@ func TestMultiOrgAlertmanager_AlertmanagerFor(t *testing.T) { } func TestMultiOrgAlertmanager_ActivateHistoricalConfiguration(t *testing.T) { - configStore := NewFakeConfigStore(t, map[int64]*models.AlertConfiguration{}) - orgStore := &FakeOrgStore{ - orgs: []int64{1, 2, 3}, - } - tmpDir := t.TempDir() - defaultConfig := `{"template_files":null,"alertmanager_config":{"route":{"receiver":"grafana-default-email","group_by":["grafana_folder","alertname"]},"templates":null,"receivers":[{"name":"grafana-default-email","grafana_managed_receiver_configs":[{"uid":"","name":"email receiver","type":"email","disableResolveMessage":false,"settings":{"addresses":"\u003cexample@email.com\u003e"},"secureSettings":null}]}]}}` - cfg := &setting.Cfg{ - DataPath: tmpDir, - UnifiedAlerting: setting.UnifiedAlertingSettings{AlertmanagerConfigPollInterval: 3 * time.Minute, DefaultConfiguration: defaultConfig}, // do not poll in tests. - } - kvStore := ngfakes.NewFakeKVStore(t) - provStore := ngfakes.NewFakeProvisioningStore() - secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore()) - decryptFn := secretsService.GetDecryptedValue - reg := prometheus.NewPedanticRegistry() - m := metrics.NewNGAlert(reg) - mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, provStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger"), secretsService, featuremgmt.WithFeatures()) - require.NoError(t, err) + mam := setupMam(t, nil) ctx := context.Background() // Ensure that one Alertmanager is created per org. @@ -339,9 +280,141 @@ func TestMultiOrgAlertmanager_ActivateHistoricalConfiguration(t *testing.T) { // Verify that the org has the old default config. cfgs, err = mam.getLatestConfigs(ctx) require.NoError(t, err) - require.Equal(t, defaultConfig, cfgs[2].AlertmanagerConfiguration) + require.JSONEq(t, defaultConfig, cfgs[2].AlertmanagerConfiguration) } +func TestMultiOrgAlertmanager_Silences(t *testing.T) { + mam := setupMam(t, nil) + ctx := context.Background() + + // Ensure that one Alertmanager is created per org. + { + require.NoError(t, mam.LoadAndSyncAlertmanagersForOrgs(ctx)) + require.Len(t, mam.alertmanagers, 3) + } + + am, err := mam.AlertmanagerFor(1) + require.NoError(t, err) + + // Confirm no silences. + silences, err := am.ListSilences(ctx, []string{}) + require.NoError(t, err) + require.Len(t, silences, 0) + + // Confirm empty state. + state, err := am.SilenceState(ctx) + require.NoError(t, err) + require.Len(t, state, 0) + + // Confirm empty kvstore. + v, ok, err := mam.kvStore.Get(ctx, 1, KVNamespace, SilencesFilename) + require.NoError(t, err) + require.False(t, ok) + require.Empty(t, v) + + // Create 2 silences. + sid, err := mam.CreateSilence(ctx, 1, GenSilence("test")) + require.NoError(t, err) + require.NotEmpty(t, sid) + sid2, err := mam.CreateSilence(ctx, 1, GenSilence("test")) + require.NoError(t, err) + require.NotEmpty(t, sid2) + + // Confirm 2 silences. + silences, err = am.ListSilences(ctx, []string{}) + require.NoError(t, err) + require.Len(t, silences, 2) + + // Confirm 2 states. + state, err = am.SilenceState(ctx) + require.NoError(t, err) + require.Len(t, state, 2) + + // Confirm 2 silences in the kvstore. + v, ok, err = mam.kvStore.Get(ctx, 1, KVNamespace, SilencesFilename) + require.NoError(t, err) + require.True(t, ok) + decoded, err := decode(v) + require.NoError(t, err) + state, err = alertingNotify.DecodeState(bytes.NewReader(decoded)) + require.NoError(t, err) + require.Len(t, state, 2) + + // Delete silence. + err = mam.DeleteSilence(ctx, 1, sid) + require.NoError(t, err) + + // Confirm silence is expired in memory. + silence, err := am.GetSilence(ctx, sid) + require.NoError(t, err) + require.EqualValues(t, types.SilenceStateExpired, *silence.Status.State) + + // Confirm silence is expired in kvstore. + v, ok, err = mam.kvStore.Get(ctx, 1, KVNamespace, SilencesFilename) + require.NoError(t, err) + require.True(t, ok) + decoded, err = decode(v) + require.NoError(t, err) + state, err = alertingNotify.DecodeState(bytes.NewReader(decoded)) + require.NoError(t, err) + require.True(t, time.Now().After(state[sid].Silence.EndsAt)) // Expired. +} + +func setupMam(t *testing.T, cfg *setting.Cfg) *MultiOrgAlertmanager { + if cfg == nil { + tmpDir := t.TempDir() + cfg = &setting.Cfg{ + DataPath: tmpDir, + UnifiedAlerting: setting.UnifiedAlertingSettings{AlertmanagerConfigPollInterval: 3 * time.Minute, DefaultConfiguration: defaultConfig}, // do not poll in tests. + } + } + + cs := NewFakeConfigStore(t, map[int64]*models.AlertConfiguration{}) + orgStore := &FakeOrgStore{ + orgs: []int64{1, 2, 3}, + } + kvStore := ngfakes.NewFakeKVStore(t) + provStore := ngfakes.NewFakeProvisioningStore() + secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore()) + decryptFn := secretsService.GetDecryptedValue + reg := prometheus.NewPedanticRegistry() + m := metrics.NewNGAlert(reg) + mam, err := NewMultiOrgAlertmanager(cfg, cs, orgStore, kvStore, provStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger"), secretsService, featuremgmt.WithFeatures()) + require.NoError(t, err) + return mam +} + +var defaultConfig = ` +{ + "template_files": null, + "alertmanager_config": { + "route": { + "receiver": "grafana-default-email", + "group_by": [ + "grafana_folder", + "alertname" + ] + }, + "templates": null, + "receivers": [ + { + "name": "grafana-default-email", + "grafana_managed_receiver_configs": [ + { + "uid": "", + "name": "email receiver", + "type": "email", + "disableResolveMessage": false, + "settings": { + "addresses": "\u003cexample@email.com\u003e"}, + "secureSettings": null + } + ] + } + ] + } +}` + var brokenConfig = ` "alertmanager_config": { "route": { diff --git a/pkg/services/ngalert/notifier/testing.go b/pkg/services/ngalert/notifier/testing.go index 3b1a6ea965a..e2edc7811a6 100644 --- a/pkg/services/ngalert/notifier/testing.go +++ b/pkg/services/ngalert/notifier/testing.go @@ -7,14 +7,18 @@ import ( "errors" "fmt" "io" + "math/rand" "testing" "time" + "github.com/go-openapi/strfmt" "github.com/matttproud/golang_protobuf_extensions/pbutil" + amv2 "github.com/prometheus/alertmanager/api/v2/models" "github.com/prometheus/alertmanager/nflog/nflogpb" "github.com/prometheus/alertmanager/silence/silencepb" "github.com/prometheus/common/model" + apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/store" @@ -357,3 +361,24 @@ func createNotificationLog(groupKey string, receiverName string, sentAt, expires ExpiresAt: expiresAt, } } + +func GenSilence(createdBy string) *apimodels.PostableSilence { + starts := strfmt.DateTime(time.Now().Add(time.Duration(rand.Int63n(9)+1) * time.Second)) + ends := strfmt.DateTime(time.Now().Add(time.Duration(rand.Int63n(9)+10) * time.Second)) + comment := "test comment" + isEqual := true + name := "test" + value := "test" + isRegex := false + matchers := amv2.Matchers{&amv2.Matcher{IsEqual: &isEqual, Name: &name, Value: &value, IsRegex: &isRegex}} + + return &apimodels.PostableSilence{ + Silence: amv2.Silence{ + Comment: &comment, + CreatedBy: &createdBy, + Matchers: matchers, + StartsAt: &starts, + EndsAt: &ends, + }, + } +} diff --git a/pkg/services/ngalert/remote/alertmanager.go b/pkg/services/ngalert/remote/alertmanager.go index f587053ab8b..66a78e6747b 100644 --- a/pkg/services/ngalert/remote/alertmanager.go +++ b/pkg/services/ngalert/remote/alertmanager.go @@ -8,6 +8,7 @@ import ( "fmt" "net/http" "net/url" + "strings" "github.com/go-openapi/strfmt" amalert "github.com/prometheus/alertmanager/api/v2/client/alert" @@ -15,6 +16,9 @@ import ( amreceiver "github.com/prometheus/alertmanager/api/v2/client/receiver" amsilence "github.com/prometheus/alertmanager/api/v2/client/silence" + alertingClusterPB "github.com/grafana/alerting/cluster/clusterpb" + alertingNotify "github.com/grafana/alerting/notify" + "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/metrics" @@ -22,8 +26,6 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/notifier" remoteClient "github.com/grafana/grafana/pkg/services/ngalert/remote/client" "github.com/grafana/grafana/pkg/services/ngalert/sender" - - alertingClusterPB "github.com/grafana/alerting/cluster/clusterpb" ) type stateStore interface { @@ -401,15 +403,30 @@ func (am *Alertmanager) Ready() bool { return am.ready } +// SilenceState returns the Alertmanager's silence state as a SilenceState. Currently, does not retrieve the state +// remotely and instead uses the value from the state store. +func (am *Alertmanager) SilenceState(ctx context.Context) (alertingNotify.SilenceState, error) { + silences, err := am.state.GetSilences(ctx) + if err != nil { + return nil, fmt.Errorf("error getting silences: %w", err) + } + + return alertingNotify.DecodeState(strings.NewReader(silences)) +} + // getFullState returns a base64-encoded protobuf message representing the Alertmanager's internal state. func (am *Alertmanager) getFullState(ctx context.Context) (string, error) { var parts []alertingClusterPB.Part - silences, err := am.state.GetSilences(ctx) + state, err := am.SilenceState(ctx) if err != nil { return "", fmt.Errorf("error getting silences: %w", err) } - parts = append(parts, alertingClusterPB.Part{Key: notifier.SilencesFilename, Data: []byte(silences)}) + b, err := state.MarshalBinary() + if err != nil { + return "", fmt.Errorf("error marshalling silences: %w", err) + } + parts = append(parts, alertingClusterPB.Part{Key: notifier.SilencesFilename, Data: b}) notificationLog, err := am.state.GetNotificationLog(ctx) if err != nil { @@ -420,7 +437,7 @@ func (am *Alertmanager) getFullState(ctx context.Context) (string, error) { fs := alertingClusterPB.FullState{ Parts: parts, } - b, err := fs.Marshal() + b, err = fs.Marshal() if err != nil { return "", fmt.Errorf("error marshaling full state: %w", err) } diff --git a/pkg/services/ngalert/remote/alertmanager_test.go b/pkg/services/ngalert/remote/alertmanager_test.go index bfd6deef0af..d11d8bf903b 100644 --- a/pkg/services/ngalert/remote/alertmanager_test.go +++ b/pkg/services/ngalert/remote/alertmanager_test.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "io" - "math/rand" "net/http" "net/http/httptest" "os" @@ -40,6 +39,12 @@ import ( const testGrafanaConfig = `{"template_files":{},"alertmanager_config":{"route":{"receiver":"grafana-default-email","group_by":["grafana_folder","alertname"]},"templates":null,"receivers":[{"name":"grafana-default-email","grafana_managed_receiver_configs":[{"uid":"","name":"some other name","type":"email","disableResolveMessage":false,"settings":{"addresses":"\u003cexample@email.com\u003e"},"secureSettings":null}]}]}}` const testGrafanaConfigWithSecret = `{"template_files":{},"alertmanager_config":{"route":{"receiver":"grafana-default-email","group_by":["grafana_folder","alertname"]},"templates":null,"receivers":[{"name":"grafana-default-email","grafana_managed_receiver_configs":[{"uid":"dde6ntuob69dtf","name":"WH","type":"webhook","disableResolveMessage":false,"settings":{"url":"http://localhost:8080","username":"test"},"secureSettings":{"password":"test"}}]}]}}` +// Valid Alertmanager state base64 encoded. +const testSilence1 = "lwEKhgEKATESFxIJYWxlcnRuYW1lGgp0ZXN0X2FsZXJ0EiMSDmdyYWZhbmFfZm9sZGVyGhF0ZXN0X2FsZXJ0X2ZvbGRlchoMCN2CkbAGEJbKrMsDIgwI7Z6RsAYQlsqsywMqCwiAkrjDmP7///8BQgxHcmFmYW5hIFRlc3RKDFRlc3QgU2lsZW5jZRIMCO2ekbAGEJbKrMsD" +const testSilence2 = "lwEKhgEKATISFxIJYWxlcnRuYW1lGgp0ZXN0X2FsZXJ0EiMSDmdyYWZhbmFfZm9sZGVyGhF0ZXN0X2FsZXJ0X2ZvbGRlchoMCN2CkbAGEJbKrMsDIgwI7Z6RsAYQlsqsywMqCwiAkrjDmP7///8BQgxHcmFmYW5hIFRlc3RKDFRlc3QgU2lsZW5jZRIMCO2ekbAGEJbKrMsDlwEKhgEKATESFxIJYWxlcnRuYW1lGgp0ZXN0X2FsZXJ0EiMSDmdyYWZhbmFfZm9sZGVyGhF0ZXN0X2FsZXJ0X2ZvbGRlchoMCN2CkbAGEJbKrMsDIgwI7Z6RsAYQlsqsywMqCwiAkrjDmP7///8BQgxHcmFmYW5hIFRlc3RKDFRlc3QgU2lsZW5jZRIMCO2ekbAGEJbKrMsD" +const testNflog1 = "OgoqCgZncm91cDESEgoJcmVjZWl2ZXIxEgV0ZXN0MyoMCIzm1bAGEPqx5uEBEgwInILWsAYQ+rHm4QE=" +const testNflog2 = "OgoqCgZncm91cDISEgoJcmVjZWl2ZXIyEgV0ZXN0MyoMCLSDkbAGEMvaofYCEgwIxJ+RsAYQy9qh9gI6CioKBmdyb3VwMRISCglyZWNlaXZlcjESBXRlc3QzKgwItIORsAYQy9qh9gISDAjEn5GwBhDL2qH2Ag==" + func TestMain(m *testing.M) { testsuite.Run(m) } @@ -145,8 +150,8 @@ func TestApplyConfig(t *testing.T) { ctx := context.Background() store := ngfakes.NewFakeKVStore(t) fstore := notifier.NewFileStore(1, store) - require.NoError(t, store.Set(ctx, cfg.OrgID, "alertmanager", notifier.SilencesFilename, "test")) - require.NoError(t, store.Set(ctx, cfg.OrgID, "alertmanager", notifier.NotificationLogFilename, "test")) + require.NoError(t, store.Set(ctx, cfg.OrgID, "alertmanager", notifier.SilencesFilename, testSilence1)) + require.NoError(t, store.Set(ctx, cfg.OrgID, "alertmanager", notifier.NotificationLogFilename, testNflog1)) // An error response from the remote Alertmanager should result in the readiness check failing. m := metrics.NewRemoteAlertmanagerMetrics(prometheus.NewRegistry()) @@ -293,14 +298,12 @@ func TestIntegrationRemoteAlertmanagerApplyConfigOnlyUploadsOnce(t *testing.T) { OrgID: 1, } - silences := []byte("test-silences") - nflog := []byte("test-notifications") store := ngfakes.NewFakeKVStore(t) fstore := notifier.NewFileStore(cfg.OrgID, store) ctx := context.Background() - require.NoError(t, store.Set(ctx, cfg.OrgID, "alertmanager", notifier.SilencesFilename, base64.StdEncoding.EncodeToString(silences))) - require.NoError(t, store.Set(ctx, cfg.OrgID, "alertmanager", notifier.NotificationLogFilename, base64.StdEncoding.EncodeToString(nflog))) + require.NoError(t, store.Set(ctx, cfg.OrgID, "alertmanager", notifier.SilencesFilename, testSilence1)) + require.NoError(t, store.Set(ctx, cfg.OrgID, "alertmanager", notifier.NotificationLogFilename, testNflog1)) secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore()) m := metrics.NewRemoteAlertmanagerMetrics(prometheus.NewRegistry()) @@ -344,8 +347,8 @@ func TestIntegrationRemoteAlertmanagerApplyConfigOnlyUploadsOnce(t *testing.T) { // Calling `ApplyConfig` again with a changed configuration and state yields no effect. { - require.NoError(t, store.Set(ctx, cfg.OrgID, "alertmanager", "silences", base64.StdEncoding.EncodeToString([]byte("abc123")))) - require.NoError(t, store.Set(ctx, cfg.OrgID, "alertmanager", "notifications", base64.StdEncoding.EncodeToString([]byte("abc123")))) + require.NoError(t, store.Set(ctx, cfg.OrgID, "alertmanager", "silences", testSilence2)) + require.NoError(t, store.Set(ctx, cfg.OrgID, "alertmanager", "notifications", testNflog2)) fakeConfig.ID = 30000000000000000 require.NoError(t, am.ApplyConfig(ctx, fakeConfig)) @@ -401,8 +404,8 @@ func TestIntegrationRemoteAlertmanagerSilences(t *testing.T) { require.Equal(t, 0, len(silences)) // Creating a silence should succeed. - testSilence := genSilence("test") - id, err := am.CreateSilence(context.Background(), &testSilence) + testSilence := notifier.GenSilence("test") + id, err := am.CreateSilence(context.Background(), testSilence) require.NoError(t, err) require.NotEmpty(t, id) testSilence.ID = id @@ -417,8 +420,8 @@ func TestIntegrationRemoteAlertmanagerSilences(t *testing.T) { require.Error(t, err) // After creating another silence, the total amount should be 2. - testSilence2 := genSilence("test") - id, err = am.CreateSilence(context.Background(), &testSilence2) + testSilence2 := notifier.GenSilence("test") + id, err = am.CreateSilence(context.Background(), testSilence2) require.NoError(t, err) require.NotEmpty(t, id) testSilence2.ID = id @@ -552,27 +555,6 @@ func TestIntegrationRemoteAlertmanagerReceivers(t *testing.T) { require.Equal(t, "empty-receiver", *rcvs[0].Name) } -func genSilence(createdBy string) apimodels.PostableSilence { - starts := strfmt.DateTime(time.Now().Add(time.Duration(rand.Int63n(9)+1) * time.Second)) - ends := strfmt.DateTime(time.Now().Add(time.Duration(rand.Int63n(9)+10) * time.Second)) - comment := "test comment" - isEqual := true - name := "test" - value := "test" - isRegex := false - matchers := amv2.Matchers{&amv2.Matcher{IsEqual: &isEqual, Name: &name, Value: &value, IsRegex: &isRegex}} - - return apimodels.PostableSilence{ - Silence: amv2.Silence{ - Comment: &comment, - CreatedBy: &createdBy, - Matchers: matchers, - StartsAt: &starts, - EndsAt: &ends, - }, - } -} - func genAlert(active bool, labels map[string]string) amv2.PostableAlert { endsAt := time.Now() if active { diff --git a/pkg/services/ngalert/remote/mock/remoteAlertmanager.go b/pkg/services/ngalert/remote/mock/remoteAlertmanager.go index f749e924b71..7e935f9cf7f 100644 --- a/pkg/services/ngalert/remote/mock/remoteAlertmanager.go +++ b/pkg/services/ngalert/remote/mock/remoteAlertmanager.go @@ -804,6 +804,60 @@ func (_c *RemoteAlertmanagerMock_SaveAndApplyDefaultConfig_Call) RunAndReturn(ru return _c } +// SilenceState provides a mock function with given fields: _a0 +func (_m *RemoteAlertmanagerMock) SilenceState(_a0 context.Context) (notify.SilenceState, error) { + ret := _m.Called(_a0) + + var r0 notify.SilenceState + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (notify.SilenceState, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(context.Context) notify.SilenceState); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(notify.SilenceState) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoteAlertmanagerMock_SilenceState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SilenceState' +type RemoteAlertmanagerMock_SilenceState_Call struct { + *mock.Call +} + +// SilenceState is a helper method to define mock.On call +// - _a0 context.Context +func (_e *RemoteAlertmanagerMock_Expecter) SilenceState(_a0 interface{}) *RemoteAlertmanagerMock_SilenceState_Call { + return &RemoteAlertmanagerMock_SilenceState_Call{Call: _e.mock.On("SilenceState", _a0)} +} + +func (_c *RemoteAlertmanagerMock_SilenceState_Call) Run(run func(_a0 context.Context)) *RemoteAlertmanagerMock_SilenceState_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *RemoteAlertmanagerMock_SilenceState_Call) Return(_a0 notify.SilenceState, _a1 error) *RemoteAlertmanagerMock_SilenceState_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *RemoteAlertmanagerMock_SilenceState_Call) RunAndReturn(run func(context.Context) (notify.SilenceState, error)) *RemoteAlertmanagerMock_SilenceState_Call { + _c.Call.Return(run) + return _c +} + // StopAndWait provides a mock function with given fields: func (_m *RemoteAlertmanagerMock) StopAndWait() { _m.Called() diff --git a/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go b/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go index 030cc607fff..37b955db493 100644 --- a/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go +++ b/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go @@ -4,6 +4,8 @@ import ( "context" "fmt" + alertingNotify "github.com/grafana/alerting/notify" + "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/models" @@ -91,6 +93,10 @@ func (fam *RemotePrimaryForkedAlertmanager) TestTemplate(ctx context.Context, c return fam.remote.TestTemplate(ctx, c) } +func (fam *RemotePrimaryForkedAlertmanager) SilenceState(ctx context.Context) (alertingNotify.SilenceState, error) { + return fam.remote.SilenceState(ctx) +} + func (fam *RemotePrimaryForkedAlertmanager) StopAndWait() { fam.internal.StopAndWait() fam.remote.StopAndWait() diff --git a/pkg/services/ngalert/remote/remote_secondary_forked_alertmanager.go b/pkg/services/ngalert/remote/remote_secondary_forked_alertmanager.go index aedb8b554cf..dd96efdbb6a 100644 --- a/pkg/services/ngalert/remote/remote_secondary_forked_alertmanager.go +++ b/pkg/services/ngalert/remote/remote_secondary_forked_alertmanager.go @@ -6,6 +6,8 @@ import ( "sync" "time" + alertingNotify "github.com/grafana/alerting/notify" + "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/models" @@ -165,6 +167,10 @@ func (fam *RemoteSecondaryForkedAlertmanager) TestTemplate(ctx context.Context, return fam.internal.TestTemplate(ctx, c) } +func (fam *RemoteSecondaryForkedAlertmanager) SilenceState(ctx context.Context) (alertingNotify.SilenceState, error) { + return fam.internal.SilenceState(ctx) +} + func (fam *RemoteSecondaryForkedAlertmanager) StopAndWait() { // Stop the internal Alertmanager. fam.internal.StopAndWait() diff --git a/public/app/features/dashboard-scene/scene/DashboardDatasourceBehaviour.test.tsx b/public/app/features/dashboard-scene/scene/DashboardDatasourceBehaviour.test.tsx new file mode 100644 index 00000000000..453b3e7983f --- /dev/null +++ b/public/app/features/dashboard-scene/scene/DashboardDatasourceBehaviour.test.tsx @@ -0,0 +1,547 @@ +import { map, of } from 'rxjs'; + +import { + DataQuery, + DataQueryRequest, + DataSourceApi, + DataSourceJsonData, + DataSourceRef, + LoadingState, + PanelData, +} from '@grafana/data'; +import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks'; +import { setPluginImportUtils } from '@grafana/runtime'; +import { SceneGridLayout, SceneQueryRunner, VizPanel } from '@grafana/scenes'; +import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard'; +import { DASHBOARD_DATASOURCE_PLUGIN_ID } from 'app/plugins/datasource/dashboard/types'; + +import { VizPanelManager } from '../panel-edit/VizPanelManager'; +import { activateFullSceneTree } from '../utils/test-utils'; + +import { DashboardDatasourceBehaviour } from './DashboardDatasourceBehaviour'; +import { DashboardGridItem } from './DashboardGridItem'; +import { DashboardScene } from './DashboardScene'; + +const grafanaDs = { + id: 1, + uid: '-- Grafana --', + name: 'grafana', + type: 'grafana', + meta: { + id: 'grafana', + }, + getRef: () => { + return { type: 'grafana', uid: '-- Grafana --' }; + }, +}; + +const dashboardDs: DataSourceApi = { + meta: { + id: DASHBOARD_DATASOURCE_PLUGIN_ID, + }, + name: SHARED_DASHBOARD_QUERY, + type: SHARED_DASHBOARD_QUERY, + uid: SHARED_DASHBOARD_QUERY, + getRef: () => { + return { type: SHARED_DASHBOARD_QUERY, uid: SHARED_DASHBOARD_QUERY }; + }, +} as DataSourceApi; + +setPluginImportUtils({ + importPanelPlugin: (id: string) => Promise.resolve(getPanelPlugin({})), + getPanelPluginFromCache: (id: string) => undefined, +}); + +const runRequestMock = jest.fn().mockImplementation((ds: DataSourceApi, request: DataQueryRequest) => { + const result: PanelData = { + state: LoadingState.Loading, + series: [], + timeRange: request.range, + request, + }; + + return of([]).pipe( + map(() => { + result.state = LoadingState.Done; + result.series = []; + + return result; + }) + ); +}); + +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + getRunRequest: () => (ds: DataSourceApi, request: DataQueryRequest) => { + return runRequestMock(ds, request); + }, + getDataSourceSrv: () => { + return { + get: async (ref: DataSourceRef) => { + if (ref.uid === 'grafana') { + return grafanaDs; + } + + if (ref.uid === SHARED_DASHBOARD_QUERY) { + return dashboardDs; + } + + return null; + }, + getInstanceSettings: jest.fn().mockResolvedValue({ uid: 'ds1' }), + }; + }, +})); + +describe('DashboardDatasourceBehaviour', () => { + describe('Given scene with a dashboard DS panel and a source panel', () => { + let scene: DashboardScene, sourcePanel: VizPanel, dashboardDSPanel: VizPanel, sceneDeactivate: () => void; + + beforeEach(async () => { + ({ scene, sourcePanel, dashboardDSPanel, sceneDeactivate } = await buildTestScene()); + }); + + it('Should re-run query of dashboardDS panel when source query re-runs', async () => { + // spy on runQueries that will be called by the behaviour + const spy = jest.spyOn(dashboardDSPanel.state.$data as SceneQueryRunner, 'runQueries'); + + // deactivate scene to mimic going into panel edit + sceneDeactivate(); + // run source panel queries and update request ID + (sourcePanel.state.$data as SceneQueryRunner).runQueries(); + + await new Promise((r) => setTimeout(r, 1)); + + // activate scene to mimic coming back from panel edit + activateFullSceneTree(scene); + + expect(spy).toHaveBeenCalledTimes(1); + }); + + it('Should not run query of dashboardDS panel when source panel queries do not change', async () => { + // spy on runQueries + const spy = jest.spyOn(dashboardDSPanel.state.$data as SceneQueryRunner, 'runQueries'); + + // deactivate scene to mimic going into panel edit + sceneDeactivate(); + + await new Promise((r) => setTimeout(r, 1)); + + // activate scene to mimic coming back from panel edit + activateFullSceneTree(scene); + + expect(spy).not.toHaveBeenCalled(); + }); + + it('Should not re-run queries in behaviour when adding a dashboardDS panel to the scene', async () => { + const sourcePanel = new VizPanel({ + title: 'Panel A', + pluginId: 'table', + key: 'panel-1', + $data: new SceneQueryRunner({ + datasource: { uid: 'grafana' }, + queries: [{ refId: 'A', queryType: 'randomWalk' }], + }), + }); + + const behaviour = new DashboardDatasourceBehaviour({}); + + const dashboardDSPanel = new VizPanel({ + title: 'Panel B', + pluginId: 'table', + key: 'panel-2', + $data: new SceneQueryRunner({ + datasource: { uid: SHARED_DASHBOARD_QUERY }, + queries: [{ refId: 'A', panelId: 1 }], + $behaviors: [behaviour], + }), + }); + + const scene = new DashboardScene({ + title: 'hello', + uid: 'dash-1', + meta: { + canEdit: true, + }, + body: new SceneGridLayout({ + children: [ + new DashboardGridItem({ + key: 'griditem-1', + x: 0, + y: 0, + width: 10, + height: 12, + body: sourcePanel, + }), + ], + }), + }); + + activateFullSceneTree(scene); + + await new Promise((r) => setTimeout(r, 1)); + + const spy = jest.spyOn(dashboardDSPanel.state.$data as SceneQueryRunner, 'runQueries'); + + const layout = scene.state.body as SceneGridLayout; + + // we add the new panel, it should run it's query as usual + layout.setState({ + children: [ + ...layout.state.children, + new DashboardGridItem({ + key: 'griditem-2', + x: 0, + y: 0, + width: 10, + height: 12, + body: dashboardDSPanel, + }), + ], + }); + + dashboardDSPanel.activate(); + + expect(spy).toHaveBeenCalledTimes(1); + // since there is no previous request ID on dashboard load, the behaviour should not re-run queries + expect(behaviour['prevRequestId']).toBeUndefined(); + }); + + it('Should not re-run queries in behaviour on scene load', async () => { + const sourcePanel = new VizPanel({ + title: 'Panel A', + pluginId: 'table', + key: 'panel-1', + $data: new SceneQueryRunner({ + datasource: { uid: 'grafana' }, + queries: [{ refId: 'A', queryType: 'randomWalk' }], + }), + }); + + const behaviour = new DashboardDatasourceBehaviour({}); + + const dashboardDSPanel = new VizPanel({ + title: 'Panel B', + pluginId: 'table', + key: 'panel-2', + $data: new SceneQueryRunner({ + datasource: { uid: SHARED_DASHBOARD_QUERY }, + queries: [{ refId: 'A', panelId: 1 }], + $behaviors: [behaviour], + }), + }); + + const scene = new DashboardScene({ + title: 'hello', + uid: 'dash-1', + meta: { + canEdit: true, + }, + body: new SceneGridLayout({ + children: [ + new DashboardGridItem({ + key: 'griditem-1', + x: 0, + y: 0, + width: 10, + height: 12, + body: sourcePanel, + }), + new DashboardGridItem({ + key: 'griditem-2', + x: 0, + y: 0, + width: 10, + height: 12, + body: dashboardDSPanel, + }), + ], + }), + }); + + const spy = jest.spyOn(dashboardDSPanel.state.$data as SceneQueryRunner, 'runQueries'); + + activateFullSceneTree(scene); + + await new Promise((r) => setTimeout(r, 1)); + + expect(spy).toHaveBeenCalledTimes(1); + // since there is no previous request ID on dashboard load, the behaviour should not re-run queries + expect(behaviour['prevRequestId']).toBeUndefined(); + }); + + it('Should exit behaviour early if not in a dashboard scene', async () => { + // spy on runQueries + const spy = jest.spyOn(dashboardDSPanel.state.$data as SceneQueryRunner, 'runQueries'); + + const vizPanelManager = new VizPanelManager({ + panel: dashboardDSPanel.clone({ $data: undefined }), + $data: dashboardDSPanel.state.$data?.clone(), + sourcePanel: dashboardDSPanel.getRef(), + }); + + vizPanelManager.activate(); + + expect(spy).not.toHaveBeenCalled(); + }); + + it('Should not re-run queries if dashboard DS panel references an invalid source panel', async () => { + const sourcePanel = new VizPanel({ + title: 'Panel A', + pluginId: 'table', + key: 'panel-1', + $data: new SceneQueryRunner({ + datasource: { uid: 'grafana' }, + queries: [{ refId: 'A', queryType: 'randomWalk' }], + }), + }); + + // query references inexistent panel + const dashboardDSPanel = new VizPanel({ + title: 'Panel B', + pluginId: 'table', + key: 'panel-2', + $data: new SceneQueryRunner({ + datasource: { uid: SHARED_DASHBOARD_QUERY }, + queries: [{ refId: 'A', panelId: 10 }], + $behaviors: [new DashboardDatasourceBehaviour({})], + }), + }); + + const scene = new DashboardScene({ + title: 'hello', + uid: 'dash-1', + meta: { + canEdit: true, + }, + body: new SceneGridLayout({ + children: [ + new DashboardGridItem({ + key: 'griditem-1', + x: 0, + y: 0, + width: 10, + height: 12, + body: sourcePanel, + }), + new DashboardGridItem({ + key: 'griditem-2', + x: 0, + y: 0, + width: 10, + height: 12, + body: dashboardDSPanel, + }), + ], + }), + }); + + const sceneDeactivate = activateFullSceneTree(scene); + + await new Promise((r) => setTimeout(r, 1)); + + // spy on runQueries + const spy = jest.spyOn(dashboardDSPanel.state.$data as SceneQueryRunner, 'runQueries'); + + // deactivate scene to mimic going into panel edit + sceneDeactivate(); + + await new Promise((r) => setTimeout(r, 1)); + + // activate scene to mimic coming back from panel edit + activateFullSceneTree(scene); + + expect(spy).not.toHaveBeenCalled(); + }); + }); + + describe('Given scene with no DashboardDS panel', () => { + it('Should not re-run queries and exit early in behaviour', async () => { + const sourcePanel = new VizPanel({ + title: 'Panel A', + pluginId: 'table', + key: 'panel-1', + $data: new SceneQueryRunner({ + datasource: { uid: 'grafana' }, + queries: [{ refId: 'A', queryType: 'randomWalk' }], + }), + }); + + const anotherPanel = new VizPanel({ + title: 'Panel B', + pluginId: 'table', + key: 'panel-2', + $data: new SceneQueryRunner({ + datasource: { uid: 'grafana' }, + queries: [{ refId: 'A', queryType: 'randomWalk' }], + }), + }); + + const scene = new DashboardScene({ + title: 'hello', + uid: 'dash-1', + meta: { + canEdit: true, + }, + body: new SceneGridLayout({ + children: [ + new DashboardGridItem({ + key: 'griditem-1', + x: 0, + y: 0, + width: 10, + height: 12, + body: sourcePanel, + }), + new DashboardGridItem({ + key: 'griditem-2', + x: 0, + y: 0, + width: 10, + height: 12, + body: anotherPanel, + }), + ], + }), + }); + + const sceneDeactivate = activateFullSceneTree(scene); + + await new Promise((r) => setTimeout(r, 1)); + + // spy on runQueries + const spy = jest.spyOn(anotherPanel.state.$data as SceneQueryRunner, 'runQueries'); + + // deactivate scene to mimic going into panel edit + sceneDeactivate(); + // run source panel queries and update request ID + (sourcePanel.state.$data as SceneQueryRunner).runQueries(); + + await new Promise((r) => setTimeout(r, 1)); + + // activate scene to mimic coming back from panel edit + activateFullSceneTree(scene); + + expect(spy).not.toHaveBeenCalled(); + }); + }); + + describe('Given an invalid state', () => { + it('Should throw an error if behaviour is not attached to a SceneQueryRunner', () => { + const behaviour = new DashboardDatasourceBehaviour({}); + + expect(() => behaviour.activate()).toThrow('DashboardDatasourceBehaviour must be attached to a SceneQueryRunner'); + }); + + it('Should throw an error if source panel does not have a SceneQueryRunner', async () => { + const sourcePanel = new VizPanel({ + title: 'Panel A', + pluginId: 'table', + key: 'panel-1', + $data: undefined, + }); + + const dashboardDSPanel = new VizPanel({ + title: 'Panel B', + pluginId: 'table', + key: 'panel-2', + $data: new SceneQueryRunner({ + datasource: { uid: SHARED_DASHBOARD_QUERY }, + queries: [{ refId: 'A', panelId: 1 }], + $behaviors: [new DashboardDatasourceBehaviour({})], + }), + }); + + const scene = new DashboardScene({ + title: 'hello', + uid: 'dash-1', + meta: { + canEdit: true, + }, + body: new SceneGridLayout({ + children: [ + new DashboardGridItem({ + key: 'griditem-1', + x: 0, + y: 0, + width: 10, + height: 12, + body: sourcePanel, + }), + new DashboardGridItem({ + key: 'griditem-2', + x: 0, + y: 0, + width: 10, + height: 12, + body: dashboardDSPanel, + }), + ], + }), + }); + + try { + activateFullSceneTree(scene); + } catch (e) { + expect(e).toEqual(new Error('Could not find SceneQueryRunner for panel')); + } + }); + }); +}); + +async function buildTestScene() { + const sourcePanel = new VizPanel({ + title: 'Panel A', + pluginId: 'table', + key: 'panel-1', + $data: new SceneQueryRunner({ + datasource: { uid: 'grafana' }, + queries: [{ refId: 'A', queryType: 'randomWalk' }], + }), + }); + + const dashboardDSPanel = new VizPanel({ + title: 'Panel B', + pluginId: 'table', + key: 'panel-2', + $data: new SceneQueryRunner({ + datasource: { uid: SHARED_DASHBOARD_QUERY }, + queries: [{ refId: 'A', panelId: 1 }], + $behaviors: [new DashboardDatasourceBehaviour({})], + }), + }); + + const scene = new DashboardScene({ + title: 'hello', + uid: 'dash-1', + meta: { + canEdit: true, + }, + body: new SceneGridLayout({ + children: [ + new DashboardGridItem({ + key: 'griditem-1', + x: 0, + y: 0, + width: 10, + height: 12, + body: sourcePanel, + }), + new DashboardGridItem({ + key: 'griditem-2', + x: 0, + y: 0, + width: 10, + height: 12, + body: dashboardDSPanel, + }), + ], + }), + }); + + const sceneDeactivate = activateFullSceneTree(scene); + + await new Promise((r) => setTimeout(r, 1)); + + return { scene, sourcePanel, dashboardDSPanel, sceneDeactivate }; +} diff --git a/public/app/features/dashboard-scene/scene/DashboardDatasourceBehaviour.tsx b/public/app/features/dashboard-scene/scene/DashboardDatasourceBehaviour.tsx new file mode 100644 index 00000000000..d39903a6ca0 --- /dev/null +++ b/public/app/features/dashboard-scene/scene/DashboardDatasourceBehaviour.tsx @@ -0,0 +1,64 @@ +import { SceneObjectBase, SceneObjectState, SceneQueryRunner, VizPanel } from '@grafana/scenes'; +import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard'; + +import { findVizPanelByKey, getDashboardSceneFor, getQueryRunnerFor, getVizPanelKeyForPanelId } from '../utils/utils'; + +import { DashboardScene } from './DashboardScene'; + +interface DashboardDatasourceBehaviourState extends SceneObjectState {} + +export class DashboardDatasourceBehaviour extends SceneObjectBase { + private prevRequestId: string | undefined; + public constructor(state: DashboardDatasourceBehaviourState) { + super(state); + + this.addActivationHandler(() => this._activationHandler()); + } + + private _activationHandler() { + const queryRunner = this.parent; + let dashboard: DashboardScene; + + if (!(queryRunner instanceof SceneQueryRunner)) { + throw new Error('DashboardDatasourceBehaviour must be attached to a SceneQueryRunner'); + } + + if (queryRunner.state.datasource?.uid !== SHARED_DASHBOARD_QUERY) { + return; + } + + try { + dashboard = getDashboardSceneFor(queryRunner); + } catch { + return; + } + + const dashboardQuery = queryRunner.state.queries.find((query) => query.panelId !== undefined); + + if (!dashboardQuery) { + return; + } + + const panelId = dashboardQuery.panelId; + const vizKey = getVizPanelKeyForPanelId(panelId); + const panel = findVizPanelByKey(dashboard, vizKey); + + if (!(panel instanceof VizPanel)) { + return; + } + + const sourcePanelQueryRunner = getQueryRunnerFor(panel); + + if (!(sourcePanelQueryRunner instanceof SceneQueryRunner)) { + throw new Error('Could not find SceneQueryRunner for panel'); + } + + if (this.prevRequestId && this.prevRequestId !== sourcePanelQueryRunner.state.data?.request?.requestId) { + queryRunner.runQueries(); + } + + return () => { + this.prevRequestId = sourcePanelQueryRunner.state.data?.request?.requestId; + }; + } +} diff --git a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx index 55e23353af9..759cadc8d04 100644 --- a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx +++ b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx @@ -167,7 +167,7 @@ export function panelMenuBehavior(menu: VizPanelMenu, isRepeat = false) { }); } - if (config.featureToggles.datatrails) { + if (config.featureToggles.exploreMetrics) { addDataTrailPanelAction(dashboard, panel, items); } diff --git a/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx b/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx index 979ad00acec..f1e669d4ac1 100644 --- a/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx @@ -11,12 +11,11 @@ import { shareDashboardType } from 'app/features/dashboard/components/ShareModal import { DashboardModel } from 'app/features/dashboard/state'; import { transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel'; +import { getVariablesCompatibility } from '../utils/getVariablesCompatibility'; import { DashboardInteractions } from '../utils/interactions'; import { SceneShareTabState } from './types'; -const exportExternallyTranslation = t('share-modal.export.share-externally-label', `Export for sharing externally`); - interface ShareExportTabState extends SceneShareTabState { isSharingExternally?: boolean; isViewingJSON?: boolean; @@ -61,7 +60,13 @@ export class ShareExportTab extends SceneObjectBase { const saveModel = transformSceneToSaveModel(dashboardRef.resolve()); const exportable = isSharingExternally - ? await this._exporter.makeExportable(new DashboardModel(saveModel)) + ? await this._exporter.makeExportable( + new DashboardModel(saveModel, undefined, { + getVariablesFromState: () => { + return getVariablesCompatibility(window.__grafanaSceneContext); + }, + }) + ) : saveModel; return exportable; @@ -100,6 +105,8 @@ function ShareExportTabRenderer({ model }: SceneComponentProps) return ''; }, [isViewingJSON]); + const exportExternallyTranslation = t('share-modal.export.share-externally-label', `Export for sharing externally`); + return ( <> {!isViewingJSON && ( diff --git a/public/app/features/dashboard-scene/utils/createPanelDataProvider.ts b/public/app/features/dashboard-scene/utils/createPanelDataProvider.ts index 774ff4c31c3..e9754ebf206 100644 --- a/public/app/features/dashboard-scene/utils/createPanelDataProvider.ts +++ b/public/app/features/dashboard-scene/utils/createPanelDataProvider.ts @@ -2,6 +2,8 @@ import { config } from '@grafana/runtime'; import { SceneDataProvider, SceneDataTransformer, SceneQueryRunner } from '@grafana/scenes'; import { PanelModel } from 'app/features/dashboard/state'; +import { DashboardDatasourceBehaviour } from '../scene/DashboardDatasourceBehaviour'; + export function createPanelDataProvider(panel: PanelModel): SceneDataProvider | undefined { // Skip setting query runner for panels without queries if (!panel.targets?.length) { @@ -25,6 +27,7 @@ export function createPanelDataProvider(panel: PanelModel): SceneDataProvider | dataLayerFilter: { panelId: panel.id, }, + $behaviors: [new DashboardDatasourceBehaviour({})], }); // Wrap inner data provider in a data transformer diff --git a/public/app/features/dashboard-scene/utils/utils.ts b/public/app/features/dashboard-scene/utils/utils.ts index 780c4b0445c..7a2bdf0fa02 100644 --- a/public/app/features/dashboard-scene/utils/utils.ts +++ b/public/app/features/dashboard-scene/utils/utils.ts @@ -13,6 +13,7 @@ import { } from '@grafana/scenes'; import { initialIntervalVariableModelState } from 'app/features/variables/interval/reducer'; +import { DashboardDatasourceBehaviour } from '../scene/DashboardDatasourceBehaviour'; import { DashboardScene } from '../scene/DashboardScene'; import { LibraryVizPanel } from '../scene/LibraryVizPanel'; import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks'; @@ -222,6 +223,7 @@ export function getDefaultVizPanel(dashboard: DashboardScene): VizPanel { $data: new SceneQueryRunner({ queries: [{ refId: 'A' }], datasource: getDataSourceRef(getDataSourceSrv().getInstanceSettings(null)!), + $behaviors: [new DashboardDatasourceBehaviour({})], }), transformations: [], }), diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx index 1577591e4bc..1bbe5fd4e9e 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx @@ -333,27 +333,28 @@ export class PanelEditorUnconnected extends PureComponent { > Discard , - this.props.panel.libraryPanel ? ( - - ) : ( - - ), + this.props.dashboard.meta.canSave && + (this.props.panel.libraryPanel ? ( + + ) : ( + + )),