Enable K8 Resources Endpoint again for Testing (#117876)

* Add some unit tests and enable sub resource so we can start testing manually on dev

* add test to verify auth headers are stripped when being forwarding requests to client

* add integration tests

* update test packages

* polish

* fix tests

* fix panic

* fmt

* white space

* add feature flag to routing of resources endpoint

* fix missing type

* update integration tests with correct feature flag

* fix test

* update other integration tests to use ff
This commit is contained in:
Tim Mulqueen 2026-02-18 15:11:13 +01:00 committed by GitHub
parent 3bb480c74a
commit e0b2ccb061
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 1287 additions and 1570 deletions

View file

@ -339,6 +339,11 @@ export interface FeatureToggles {
*/
datasourcesRerouteLegacyCRUDAPIs?: boolean;
/**
* Handle datasource resource requests to the legacy API routes by querying the new datasource api group endpoints behind the scenes.
* @default false
*/
datasourcesApiServerEnableResourceEndpoint?: boolean;
/**
* Runs CloudWatch metrics queries as separate batches
* @default false
*/

View file

@ -138,6 +138,7 @@ func TestGetK8sDataSourceByUIDHandler(t *testing.T) {
featuremgmt.FlagDatasourcesRerouteLegacyCRUDAPIs,
featuremgmt.FlagQueryService,
featuremgmt.FlagQueryServiceWithConnections,
featuremgmt.FlagDatasourcesApiServerEnableResourceEndpoint,
),
dsConnectionClient: &mockConnectionClient{result: tt.connectionResult, err: tt.connectionErr},
clientConfigProvider: &mockDirectRestConfigProvider{host: "http://localhost", transport: &mockRoundTripper{statusCode: tt.statusCode, responseBody: tt.responseBody}},

View file

@ -39,8 +39,9 @@ var (
)
type DataSourceAPIBuilderConfig struct {
LoadQueryTypes bool
UseDualWriter bool
LoadQueryTypes bool
UseDualWriter bool
EnableResourceEndpoint bool
}
// DataSourceAPIBuilder is used just so wire has something unique to return
@ -106,8 +107,9 @@ func RegisterAPIService(
accessControl,
//nolint:staticcheck // not yet migrated to OpenFeature
DataSourceAPIBuilderConfig{
LoadQueryTypes: features.IsEnabledGlobally(featuremgmt.FlagDatasourceQueryTypes),
UseDualWriter: features.IsEnabledGlobally(featuremgmt.FlagQueryServiceWithConnections),
LoadQueryTypes: features.IsEnabledGlobally(featuremgmt.FlagDatasourceQueryTypes),
UseDualWriter: features.IsEnabledGlobally(featuremgmt.FlagQueryServiceWithConnections),
EnableResourceEndpoint: features.IsEnabledGlobally(featuremgmt.FlagDatasourcesApiServerEnableResourceEndpoint),
},
)
if err != nil {
@ -246,7 +248,10 @@ func (b *DataSourceAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver
ds := b.datasourceResourceInfo
storage[ds.StoragePath("query")] = &subQueryREST{builder: b}
storage[ds.StoragePath("health")] = &subHealthREST{builder: b}
storage[ds.StoragePath("resource")] = &subResourceREST{builder: b}
if b.cfg.EnableResourceEndpoint {
storage[ds.StoragePath("resource")] = &subResourceREST{builder: b}
}
// FIXME: temporarily register both "datasources" and "connections" query paths
// This lets us deploy both datasources/{uid}/query and connections/{uid}/query

View file

@ -2,19 +2,20 @@ package datasource
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/rest"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/plugins/httpresponsesender"
"github.com/grafana/grafana/pkg/services/datasources"
)
type subResourceREST struct {
@ -47,37 +48,30 @@ func (r *subResourceREST) NewConnectOptions() (runtime.Object, bool, string) {
return nil, true, ""
}
// FIXME: this endpoint has not been tested yet, so it is not enabled by default.
// It is especially important to make sure the `ClearAuthHeadersMiddleware` is active,
// when using this endpoint.
var resourceEnabled = false
func (r *subResourceREST) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) {
if !resourceEnabled {
return nil, &apierrors.StatusError{
ErrStatus: metav1.Status{
Status: metav1.StatusFailure,
Code: http.StatusNotImplemented,
},
}
}
pluginCtx, err := r.builder.getPluginContext(ctx, name)
if err != nil {
backend.Logger.Error("failed to get plugin context for datasource in resource handler", "name", name, "error", err)
if errors.Is(err, datasources.ErrDataSourceNotFound) {
return nil, r.builder.datasourceResourceInfo.NewNotFound(name)
}
return nil, err
}
ctx = backend.WithGrafanaConfig(ctx, pluginCtx.GrafanaConfig)
ctx = contextualMiddlewares(ctx)
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
clonedReq, err := resourceRequest(req)
if err != nil {
backend.Logger.Error("failed to create resource request", "error", err)
responder.Error(err)
return
}
body, err := io.ReadAll(req.Body)
if err != nil {
backend.Logger.Error("failed to read request body", "error", err)
responder.Error(err)
return
}
@ -92,6 +86,7 @@ func (r *subResourceREST) Connect(ctx context.Context, name string, opts runtime
}, httpresponsesender.New(w))
if err != nil {
backend.Logger.Error("plugin resource request failed", "error", err)
responder.Error(err)
}
}), nil

View file

@ -1,13 +1,95 @@
package datasource
import (
"bytes"
"context"
"errors"
"net/http"
"net/http/httptest"
"testing"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime"
datasourceV0 "github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
)
type resourceMockClient struct {
callResourceFunc func(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error
}
func (m *resourceMockClient) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
return nil, nil
}
func (m *resourceMockClient) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
return nil, nil
}
func (m *resourceMockClient) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
if m.callResourceFunc != nil {
return m.callResourceFunc(ctx, req, sender)
}
return nil
}
func (m *resourceMockClient) QueryChunkedData(ctx context.Context, req *backend.QueryChunkedDataRequest, w backend.ChunkedDataWriter) error {
return nil
}
func (m *resourceMockClient) ConvertObjects(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
return nil, nil
}
type resourceMockDatasourceProvider struct {
instanceSettings *backend.DataSourceInstanceSettings
instanceSettingsErr error
}
func (m *resourceMockDatasourceProvider) GetDataSource(ctx context.Context, uid string) (*datasourceV0.DataSource, error) {
return nil, nil
}
func (m *resourceMockDatasourceProvider) ListDataSources(ctx context.Context) (*datasourceV0.DataSourceList, error) {
return nil, nil
}
func (m *resourceMockDatasourceProvider) CreateDataSource(ctx context.Context, ds *datasourceV0.DataSource) (*datasourceV0.DataSource, error) {
return nil, nil
}
func (m *resourceMockDatasourceProvider) UpdateDataSource(ctx context.Context, ds *datasourceV0.DataSource) (*datasourceV0.DataSource, error) {
return nil, nil
}
func (m *resourceMockDatasourceProvider) DeleteDataSource(ctx context.Context, uid string) error {
return nil
}
func (m *resourceMockDatasourceProvider) GetInstanceSettings(ctx context.Context, uid string) (*backend.DataSourceInstanceSettings, error) {
return m.instanceSettings, m.instanceSettingsErr
}
type resourceMockContextProvider struct {
pluginCtx backend.PluginContext
pluginCtxErr error
}
func (m *resourceMockContextProvider) PluginContextForDataSource(ctx context.Context, datasourceSettings *backend.DataSourceInstanceSettings) (backend.PluginContext, error) {
return m.pluginCtx, m.pluginCtxErr
}
type resourceMockResponder struct {
lastErr error
}
func (m *resourceMockResponder) Object(statusCode int, obj runtime.Object) {}
func (m *resourceMockResponder) Error(err error) {
m.lastErr = err
}
func TestResourceRequest(t *testing.T) {
testCases := []struct {
desc string
@ -69,3 +151,304 @@ func TestResourceRequest(t *testing.T) {
})
}
}
func TestSubResourceREST_Connect(t *testing.T) {
t.Run("returns error when GetInstanceSettings fails", func(t *testing.T) {
mockProvider := &resourceMockDatasourceProvider{
instanceSettingsErr: errors.New("datasource not found"),
}
mockContext := &resourceMockContextProvider{}
builder := &DataSourceAPIBuilder{
datasources: mockProvider,
contextProvider: mockContext,
}
r := &subResourceREST{builder: builder}
responder := &resourceMockResponder{}
handler, err := r.Connect(context.Background(), "test-ds", nil, responder)
require.Error(t, err)
require.Nil(t, handler)
require.Contains(t, err.Error(), "datasource not found")
})
t.Run("returns error when PluginContextForDataSource fails", func(t *testing.T) {
mockProvider := &resourceMockDatasourceProvider{
instanceSettings: &backend.DataSourceInstanceSettings{
UID: "test-ds",
Name: "Test Datasource",
},
}
mockContext := &resourceMockContextProvider{
pluginCtxErr: errors.New("failed to create plugin context"),
}
builder := &DataSourceAPIBuilder{
datasources: mockProvider,
contextProvider: mockContext,
}
r := &subResourceREST{builder: builder}
responder := &resourceMockResponder{}
handler, err := r.Connect(context.Background(), "test-ds", nil, responder)
require.Error(t, err)
require.Nil(t, handler)
require.Contains(t, err.Error(), "failed to create plugin context")
})
t.Run("successfully creates handler and forwards request", func(t *testing.T) {
var capturedRequest *backend.CallResourceRequest
mockClient := &resourceMockClient{
callResourceFunc: func(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
capturedRequest = req
return sender.Send(&backend.CallResourceResponse{
Status: http.StatusOK,
Headers: map[string][]string{"Content-Type": {"application/json"}},
Body: []byte(`{"message": "success"}`),
})
},
}
mockProvider := &resourceMockDatasourceProvider{
instanceSettings: &backend.DataSourceInstanceSettings{
UID: "test-ds",
Name: "Test Datasource",
},
}
mockContext := &resourceMockContextProvider{
pluginCtx: backend.PluginContext{
DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{
UID: "test-ds",
Name: "Test Datasource",
},
},
}
builder := &DataSourceAPIBuilder{
client: mockClient,
datasources: mockProvider,
contextProvider: mockContext,
}
r := &subResourceREST{builder: builder}
responder := &resourceMockResponder{}
handler, err := r.Connect(context.Background(), "test-ds", nil, responder)
require.NoError(t, err)
require.NotNil(t, handler)
reqBody := []byte(`{"test": "data"}`)
req := httptest.NewRequest(http.MethodPost, "http://localhost/apis/test.datasource.grafana.app/v0alpha1/namespaces/default/datasources/test-ds/resource/some/path?key=value", bytes.NewReader(reqBody))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Custom-Header", "custom-value")
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, req)
require.NotNil(t, capturedRequest)
require.Equal(t, http.MethodPost, capturedRequest.Method)
require.Equal(t, "some/path", capturedRequest.Path)
require.Equal(t, "some/path?key=value", capturedRequest.URL)
require.Equal(t, reqBody, capturedRequest.Body)
require.Contains(t, capturedRequest.Headers["Content-Type"], "application/json")
require.Contains(t, capturedRequest.Headers["X-Custom-Header"], "custom-value")
require.Equal(t, http.StatusOK, recorder.Code)
require.Equal(t, `{"message": "success"}`, recorder.Body.String())
})
t.Run("forwards all HTTP methods correctly", func(t *testing.T) {
methods := []string{
http.MethodGet,
http.MethodPost,
http.MethodPut,
http.MethodPatch,
http.MethodDelete,
http.MethodHead,
http.MethodOptions,
}
for _, method := range methods {
t.Run(method, func(t *testing.T) {
var capturedMethod string
mockClient := &resourceMockClient{
callResourceFunc: func(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
capturedMethod = req.Method
return sender.Send(&backend.CallResourceResponse{
Status: http.StatusOK,
Body: []byte("ok"),
})
},
}
mockProvider := &resourceMockDatasourceProvider{
instanceSettings: &backend.DataSourceInstanceSettings{UID: "test-ds"},
}
mockContext := &resourceMockContextProvider{
pluginCtx: backend.PluginContext{},
}
builder := &DataSourceAPIBuilder{
client: mockClient,
datasources: mockProvider,
contextProvider: mockContext,
}
r := &subResourceREST{builder: builder}
handler, err := r.Connect(context.Background(), "test-ds", nil, &resourceMockResponder{})
require.NoError(t, err)
req := httptest.NewRequest(method, "http://localhost/apis/test/resource/path", nil)
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, req)
require.Equal(t, method, capturedMethod)
})
}
})
t.Run("handles CallResource error", func(t *testing.T) {
mockClient := &resourceMockClient{
callResourceFunc: func(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
return errors.New("plugin error")
},
}
mockProvider := &resourceMockDatasourceProvider{
instanceSettings: &backend.DataSourceInstanceSettings{UID: "test-ds"},
}
mockContext := &resourceMockContextProvider{
pluginCtx: backend.PluginContext{},
}
builder := &DataSourceAPIBuilder{
client: mockClient,
datasources: mockProvider,
contextProvider: mockContext,
}
r := &subResourceREST{builder: builder}
responder := &resourceMockResponder{}
handler, err := r.Connect(context.Background(), "test-ds", nil, responder)
require.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "http://localhost/apis/test/resource/path", nil)
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, req)
// The responder should have received the error
require.NotNil(t, responder.lastErr)
require.Contains(t, responder.lastErr.Error(), "plugin error")
})
t.Run("forwards request body to plugin", func(t *testing.T) {
var capturedBody []byte
mockClient := &resourceMockClient{
callResourceFunc: func(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
capturedBody = req.Body
return sender.Send(&backend.CallResourceResponse{
Status: http.StatusOK,
})
},
}
mockProvider := &resourceMockDatasourceProvider{
instanceSettings: &backend.DataSourceInstanceSettings{UID: "test-ds"},
}
mockContext := &resourceMockContextProvider{
pluginCtx: backend.PluginContext{},
}
builder := &DataSourceAPIBuilder{
client: mockClient,
datasources: mockProvider,
contextProvider: mockContext,
}
r := &subResourceREST{builder: builder}
handler, err := r.Connect(context.Background(), "test-ds", nil, &resourceMockResponder{})
require.NoError(t, err)
requestBody := `{"key": "value", "nested": {"foo": "bar"}}`
req := httptest.NewRequest(http.MethodPost, "http://localhost/apis/test/resource/path", bytes.NewBufferString(requestBody))
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, req)
require.Equal(t, requestBody, string(capturedBody))
})
t.Run("handles empty request body", func(t *testing.T) {
var capturedBody []byte
mockClient := &resourceMockClient{
callResourceFunc: func(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
capturedBody = req.Body
return sender.Send(&backend.CallResourceResponse{
Status: http.StatusOK,
})
},
}
mockProvider := &resourceMockDatasourceProvider{
instanceSettings: &backend.DataSourceInstanceSettings{UID: "test-ds"},
}
mockContext := &resourceMockContextProvider{
pluginCtx: backend.PluginContext{},
}
builder := &DataSourceAPIBuilder{
client: mockClient,
datasources: mockProvider,
contextProvider: mockContext,
}
r := &subResourceREST{builder: builder}
handler, err := r.Connect(context.Background(), "test-ds", nil, &resourceMockResponder{})
require.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "http://localhost/apis/test/resource/path", nil)
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, req)
require.Empty(t, capturedBody)
})
t.Run("preserves query parameters in URL", func(t *testing.T) {
var capturedURL string
var capturedPath string
mockClient := &resourceMockClient{
callResourceFunc: func(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
capturedURL = req.URL
capturedPath = req.Path
return sender.Send(&backend.CallResourceResponse{
Status: http.StatusOK,
})
},
}
mockProvider := &resourceMockDatasourceProvider{
instanceSettings: &backend.DataSourceInstanceSettings{UID: "test-ds"},
}
mockContext := &resourceMockContextProvider{
pluginCtx: backend.PluginContext{},
}
builder := &DataSourceAPIBuilder{
client: mockClient,
datasources: mockProvider,
contextProvider: mockContext,
}
r := &subResourceREST{builder: builder}
handler, err := r.Connect(context.Background(), "test-ds", nil, &resourceMockResponder{})
require.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "http://localhost/apis/test/resource/api/endpoint?foo=bar&baz=qux", nil)
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, req)
require.Equal(t, "api/endpoint", capturedPath)
require.Equal(t, "api/endpoint?foo=bar&baz=qux", capturedURL)
})
}

View file

@ -516,6 +516,14 @@ var (
RequiresRestart: false,
Expression: "false",
},
{
Name: "datasourcesApiServerEnableResourceEndpoint",
Description: "Handle datasource resource requests to the legacy API routes by querying the new datasource api group endpoints behind the scenes.",
Stage: FeatureStageExperimental,
Owner: grafanaDatasourcesCoreServicesSquad,
RequiresRestart: false,
Expression: "false",
},
{
Name: "cloudWatchBatchQueries",
Description: "Runs CloudWatch metrics queries as separate batches",

View file

@ -13,7 +13,7 @@ Created,Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
2024-10-23,lokiShardSplitting,experimental,@grafana/observability-logs,false,false,true
2023-02-09,lokiQuerySplitting,GA,@grafana/observability-logs,false,false,true
2022-02-09,influxdbBackendMigration,GA,@grafana/partner-datasources,false,false,true
2026-01-19,liveAPIServer,experimental,@grafana/grafana-app-platform-squad,true,true,false
2026-02-18,liveAPIServer,experimental,@grafana/grafana-app-platform-squad,true,true,false
2025-08-29,starsFromAPIServer,experimental,@grafana/grafana-frontend-navigation,false,false,true
2025-08-29,kubernetesStars,experimental,@grafana/grafana-app-platform-squad,false,true,false
2023-11-29,influxqlStreamingParser,experimental,@grafana/partner-datasources,false,false,false
@ -24,7 +24,7 @@ Created,Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
2023-06-06,refactorVariablesTimeRange,preview,@grafana/dashboards-squad,false,false,false
2023-05-05,faroDatasourceSelector,preview,@grafana/app-o11y,false,false,true
2023-04-24,enableDatagridEditing,preview,@grafana/dataviz-squad,false,false,false
2026-02-10,faroSessionReplay,experimental,@grafana/session-replay,false,false,true
2026-02-18,faroSessionReplay,experimental,@grafana/session-replay,false,false,true
2023-07-12,logsExploreTableVisualisation,GA,@grafana/observability-logs,false,false,true
2023-07-06,awsDatasourcesTempCredentials,GA,@grafana/aws-datasources,false,false,false
2023-07-13,mlExpressions,experimental,@grafana/alerting-squad,false,false,false
@ -42,11 +42,11 @@ Created,Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
2023-09-19,lokiRunQueriesInParallel,privatePreview,@grafana/oss-big-tent,false,false,false
2023-09-28,externalServiceAccounts,preview,@grafana/identity-access-team,false,false,false
2023-12-05,kubernetesSnapshots,experimental,@grafana/grafana-app-platform-squad,false,true,false
2025-06-25,kubernetesLibraryPanels,experimental,@grafana/grafana-app-platform-squad,false,true,false
2025-06-26,kubernetesLibraryPanels,experimental,@grafana/grafana-app-platform-squad,false,true,false
2024-06-05,kubernetesDashboards,GA,@grafana/dashboards-squad,false,false,false
2025-07-31,kubernetesShortURLs,experimental,@grafana/grafana-app-platform-squad,false,true,false
2025-08-01,kubernetesShortURLs,experimental,@grafana/grafana-app-platform-squad,false,true,false
2025-08-29,useKubernetesShortURLsAPI,experimental,@grafana/sharing-squad,false,false,true
2025-07-31,kubernetesAlertingRules,experimental,@grafana/alerting-squad,false,true,false
2025-08-01,kubernetesAlertingRules,experimental,@grafana/alerting-squad,false,true,false
2025-08-29,kubernetesCorrelations,experimental,@grafana/datapro,false,true,false
2025-12-09,kubernetesUnifiedStorageQuotas,experimental,@grafana/search-and-storage,false,true,false
2025-10-16,kubernetesLogsDrilldown,experimental,@grafana/observability-logs,false,true,false
@ -56,13 +56,14 @@ Created,Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
2025-04-11,dashboardSchemaValidationLogging,experimental,@grafana/grafana-app-platform-squad,false,false,false
2025-07-30,scanRowInvalidDashboardParseFallbackEnabled,experimental,@grafana/search-and-storage,false,false,false
2024-05-23,datasourceQueryTypes,experimental,@grafana/grafana-app-platform-squad,false,true,false
2026-01-22,datasourceDisableIdApi,experimental,@grafana/grafana-datasources-core-services,false,true,false
2026-02-18,datasourceDisableIdApi,experimental,@grafana/grafana-datasources-core-services,false,true,false
2024-04-19,queryService,experimental,@grafana/grafana-datasources-core-services,false,true,false
2025-08-28,queryServiceWithConnections,experimental,@grafana/grafana-datasources-core-services,false,true,false
2026-02-13,useNewAPIsForDatasourceCRUD,experimental,@grafana/grafana-datasources-core-services,false,true,true
2026-02-18,useNewAPIsForDatasourceCRUD,experimental,@grafana/grafana-datasources-core-services,false,true,true
2024-04-19,queryServiceRewrite,experimental,@grafana/grafana-datasources-core-services,false,true,false
2024-04-19,queryServiceFromUI,experimental,@grafana/grafana-datasources-core-services,false,false,true
2026-01-19,datasourcesRerouteLegacyCRUDAPIs,experimental,@grafana/grafana-datasources-core-services,false,false,false
2026-02-18,datasourcesRerouteLegacyCRUDAPIs,experimental,@grafana/grafana-datasources-core-services,false,false,false
2026-02-18,datasourcesApiServerEnableResourceEndpoint,experimental,@grafana/grafana-datasources-core-services,false,false,false
2023-10-20,cloudWatchBatchQueries,GA,@grafana/aws-datasources,false,false,false
2023-10-12,cachingOptimizeSerializationMemoryUsage,experimental,@grafana/grafana-operator-experience-squad,false,false,false
2023-10-30,alertmanagerRemoteSecondary,experimental,@grafana/alerting-squad,false,false,false
@ -81,7 +82,7 @@ Created,Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
2025-12-08,perPanelFiltering,experimental,@grafana/dashboards-squad,false,false,true
2023-11-03,panelFilterVariable,experimental,@grafana/dashboards-squad,false,false,true
2023-11-06,pdfTables,preview,@grafana/grafana-operator-experience-squad,false,false,false
2026-02-16,reportingV2Layouts,experimental,@grafana/grafana-operator-experience-squad,false,false,true
2026-02-18,reportingV2Layouts,experimental,@grafana/grafana-operator-experience-squad,false,false,true
2024-01-02,canvasPanelPanZoom,preview,@grafana/dataviz-squad,false,false,true
2025-07-24,timeComparison,experimental,@grafana/dataviz-squad,false,false,true
2023-12-13,tableSharedCrosshair,experimental,@grafana/dataviz-squad,false,false,true
@ -89,8 +90,8 @@ Created,Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
2024-01-10,alertingQueryOptimization,GA,@grafana/alerting-squad,false,false,false
2024-01-18,jitterAlertRulesWithinGroups,preview,@grafana/alerting-squad,false,true,false
2025-12-29,auditLoggingAppPlatform,experimental,@grafana/grafana-operator-experience-squad,false,true,false
2025-07-31,secretsManagementAppPlatformUI,experimental,@grafana/grafana-operator-experience-squad,false,false,false
2026-02-04,secretsKeeperUI,experimental,@grafana/grafana-operator-experience-squad,false,false,true
2025-08-01,secretsManagementAppPlatformUI,experimental,@grafana/grafana-operator-experience-squad,false,false,false
2026-02-18,secretsKeeperUI,experimental,@grafana/grafana-operator-experience-squad,false,false,true
2024-01-23,alertingSaveStatePeriodic,privatePreview,@grafana/alerting-squad,false,false,false
2025-01-27,alertingSaveStateCompressed,preview,@grafana/alerting-squad,false,false,false
2024-11-27,scopeApi,experimental,@grafana/grafana-app-platform-squad,false,false,false
@ -111,16 +112,16 @@ Created,Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
2024-04-16,disableNumericMetricsSortingInExpressions,experimental,@grafana/oss-big-tent,false,true,false
2024-04-22,grafanaManagedRecordingRules,experimental,@grafana/alerting-squad,false,false,false
2022-10-07,queryLibrary,preview,@grafana/sharing-squad,false,false,false
2026-01-19,savedQueriesRBAC,preview,@grafana/sharing-squad,false,false,false
2026-02-18,savedQueriesRBAC,preview,@grafana/sharing-squad,false,false,false
2025-08-29,dashboardLibrary,experimental,@grafana/sharing-squad,false,false,false
2025-11-07,suggestedDashboards,experimental,@grafana/sharing-squad,false,false,false
2026-01-21,dashboardValidatorApp,experimental,@grafana/sharing-squad,false,false,false
2026-02-18,dashboardValidatorApp,experimental,@grafana/sharing-squad,false,false,false
2025-10-28,dashboardTemplates,preview,@grafana/sharing-squad,false,false,false
2024-05-24,alertingListViewV2,privatePreview,@grafana/alerting-squad,false,false,true
2026-01-14,alertingNavigationV2,experimental,@grafana/alerting-squad,false,false,false
2025-12-19,alertingSavedSearches,experimental,@grafana/alerting-squad,false,false,true
2024-05-23,alertingDisableSendAlertsExternal,experimental,@grafana/alerting-squad,false,false,false
2026-02-06,alertingSyncNotifiersApiMigration,experimental,@grafana/alerting-squad,false,false,true
2026-02-18,alertingSyncNotifiersApiMigration,experimental,@grafana/alerting-squad,false,false,true
2024-05-27,preserveDashboardStateWhenNavigating,experimental,@grafana/dashboards-squad,false,false,false
2024-05-29,alertingCentralAlertHistory,experimental,@grafana/alerting-squad,false,false,false
2024-06-05,pluginProxyPreserveTrailingSlash,GA,@grafana/plugins-platform-backend,false,false,false
@ -135,7 +136,7 @@ Created,Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
2024-09-11,alertingFilterV2,experimental,@grafana/alerting-squad,false,false,false
2024-08-09,dataplaneAggregator,experimental,@grafana/grafana-app-platform-squad,false,true,false
2024-08-30,newFiltersUI,GA,@grafana/dashboards-squad,false,false,false
2025-07-31,vizActionsAuth,preview,@grafana/dataviz-squad,false,false,true
2025-08-01,vizActionsAuth,preview,@grafana/dataviz-squad,false,false,true
2024-09-27,alertingPrometheusRulesPrimary,experimental,@grafana/alerting-squad,false,false,true
2024-08-29,exploreLogsShardSplitting,experimental,@grafana/observability-logs,false,false,true
2024-08-29,exploreLogsAggregatedMetrics,experimental,@grafana/observability-logs,false,false,true
@ -164,7 +165,7 @@ Created,Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
2025-07-23,alertingAIFeedback,experimental,@grafana/alerting-squad,false,false,false
2025-07-16,alertingAIImproveAlertRules,experimental,@grafana/alerting-squad,false,false,false
2025-07-16,alertingAIGenTemplates,experimental,@grafana/alerting-squad,false,false,false
2025-07-31,alertingEnrichmentPerRule,experimental,@grafana/alerting-squad,false,false,false
2025-08-01,alertingEnrichmentPerRule,experimental,@grafana/alerting-squad,false,false,false
2025-08-29,alertingEnrichmentAssistantInvestigations,experimental,@grafana/alerting-squad,false,false,false
2025-07-16,alertingAIAnalyzeCentralStateHistory,experimental,@grafana/alerting-squad,false,false,false
2024-11-22,alertingNotificationsStepMode,GA,@grafana/alerting-squad,false,false,true
@ -174,7 +175,7 @@ Created,Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
2024-12-27,k8SFolderCounts,experimental,@grafana/search-and-storage,false,false,false
2025-01-09,improvedExternalSessionHandlingSAML,GA,@grafana/identity-access-team,false,false,false
2025-05-22,teamHttpHeadersTempo,experimental,@grafana/identity-access-team,false,false,false
2026-02-05,teamHttpHeadersFromAppPlatform,experimental,@grafana/identity-access-team,false,false,false
2026-02-18,teamHttpHeadersFromAppPlatform,experimental,@grafana/identity-access-team,false,false,false
2025-01-20,grafanaAdvisor,privatePreview,@grafana/plugins-platform-backend,false,false,false
2025-01-15,elasticsearchImprovedParsing,experimental,@grafana/partner-datasources,false,false,false
2025-01-21,datasourceConnectionsTab,privatePreview,@grafana/plugins-platform-backend,false,false,true
@ -194,7 +195,7 @@ Created,Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
2025-03-14,infinityRunQueriesInParallel,privatePreview,@grafana/oss-big-tent,false,false,false
2025-03-14,alertingMigrationUI,GA,@grafana/alerting-squad,false,false,true
2025-05-21,alertingImportYAMLUI,GA,@grafana/alerting-squad,false,false,true
2026-01-27,alertingMigrationWizardUI,experimental,@grafana/alerting-squad,false,false,true
2026-02-18,alertingMigrationWizardUI,experimental,@grafana/alerting-squad,false,false,true
2025-04-02,azureMonitorLogsBuilderEditor,preview,@grafana/partner-datasources,false,false,false
2025-03-31,localeFormatPreference,deprecated,@grafana/grafana-frontend-platform,false,false,false
2025-03-21,unifiedStorageGrpcConnectionPool,experimental,@grafana/search-and-storage,false,false,false
@ -204,7 +205,7 @@ Created,Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
2025-04-09,unifiedNavbars,GA,@grafana/plugins-platform-backend,false,false,true
2025-04-07,logsPanelControls,preview,@grafana/observability-logs,false,false,true
2025-04-09,metricsFromProfiles,experimental,@grafana/observability-traces-and-profiling,false,false,true
2025-07-31,grafanaAssistantInProfilesDrilldown,GA,@grafana/observability-traces-and-profiling,false,false,true
2025-08-01,grafanaAssistantInProfilesDrilldown,GA,@grafana/observability-traces-and-profiling,false,false,true
2025-07-15,tempoAlerting,experimental,@grafana/observability-traces-and-profiling,false,false,false
2025-04-16,pluginsAutoUpdate,experimental,@grafana/plugins-platform-backend,false,false,false
2025-04-22,alertingListViewV2PreviewToggle,privatePreview,@grafana/alerting-squad,false,false,true
@ -212,14 +213,14 @@ Created,Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
2025-04-24,alertingBulkActionsInUI,GA,@grafana/alerting-squad,false,false,true
2025-06-18,kubernetesAuthzApis,deprecated,@grafana/identity-access-team,false,false,false
2025-08-29,kubernetesAuthZHandlerRedirect,deprecated,@grafana/identity-access-team,false,false,false
2026-02-04,kubernetesAuthZResourcePermissionsRedirect,experimental,@grafana/identity-access-team,false,false,false
2026-02-04,kubernetesAuthZRolesRedirect,experimental,@grafana/identity-access-team,false,false,false
2025-07-31,kubernetesAuthzResourcePermissionApis,experimental,@grafana/identity-access-team,false,false,false
2026-02-18,kubernetesAuthZResourcePermissionsRedirect,experimental,@grafana/identity-access-team,false,false,false
2026-02-18,kubernetesAuthZRolesRedirect,experimental,@grafana/identity-access-team,false,false,false
2025-08-01,kubernetesAuthzResourcePermissionApis,experimental,@grafana/identity-access-team,false,false,false
2025-10-13,kubernetesAuthzZanzanaSync,experimental,@grafana/identity-access-team,false,false,false
2026-01-12,kubernetesAuthzCoreRolesApi,experimental,@grafana/identity-access-team,false,false,false
2026-01-15,kubernetesAuthzGlobalRolesApi,experimental,@grafana/identity-access-team,false,false,false
2026-01-12,kubernetesAuthzRolesApi,experimental,@grafana/identity-access-team,false,false,false
2026-01-14,kubernetesAuthzTeamLBACRuleApi,experimental,@grafana/identity-access-team,false,false,false
2026-02-18,kubernetesAuthzTeamLBACRuleApi,experimental,@grafana/identity-access-team,false,false,false
2026-01-12,kubernetesAuthzRoleBindingsApi,experimental,@grafana/identity-access-team,false,false,false
2025-07-25,kubernetesAuthnMutation,experimental,@grafana/identity-access-team,false,false,false
2025-11-25,kubernetesExternalGroupMapping,experimental,@grafana/identity-access-team,false,false,false
@ -227,11 +228,11 @@ Created,Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
2025-12-10,recentlyViewedDashboards,experimental,@grafana/grafana-frontend-navigation,false,false,true
2026-01-13,experimentRecentlyViewedDashboards,experimental,@grafana/grafana-frontend-navigation,false,false,true
2025-06-06,alertEnrichment,experimental,@grafana/alerting-squad,false,false,false
2025-07-31,alertEnrichmentMultiStep,experimental,@grafana/alerting-squad,false,false,false
2025-07-31,alertEnrichmentConditional,experimental,@grafana/alerting-squad,false,false,false
2025-08-01,alertEnrichmentMultiStep,experimental,@grafana/alerting-squad,false,false,false
2025-08-01,alertEnrichmentConditional,experimental,@grafana/alerting-squad,false,false,false
2025-06-10,alertingImportAlertmanagerAPI,experimental,@grafana/alerting-squad,false,false,false
2025-07-31,alertingImportAlertmanagerUI,experimental,@grafana/alerting-squad,false,false,false
2026-01-26,alertingDisableDMAinUI,experimental,@grafana/alerting-squad,false,false,false
2025-08-01,alertingImportAlertmanagerUI,experimental,@grafana/alerting-squad,false,false,false
2026-02-18,alertingDisableDMAinUI,experimental,@grafana/alerting-squad,false,false,false
2025-07-15,sharingDashboardImage,GA,@grafana/sharing-squad,false,false,true
2025-06-17,preferLibraryPanelTitle,privatePreview,@grafana/dashboards-squad,false,false,false
2025-06-24,tabularNumbers,GA,@grafana/grafana-frontend-platform,false,false,false
@ -244,33 +245,33 @@ Created,Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
2025-07-18,unifiedStorageSearchDualReaderEnabled,experimental,@grafana/search-and-storage,false,false,false
2025-07-31,dashboardLevelTimeMacros,experimental,@grafana/dashboards-squad,false,false,true
2025-07-25,alertmanagerRemoteSecondaryWithRemoteState,experimental,@grafana/alerting-squad,false,false,false
2025-07-31,restrictedPluginApis,experimental,@grafana/plugins-platform-backend,false,false,true
2025-07-31,favoriteDatasources,experimental,@grafana/plugins-platform-backend,false,false,true
2025-07-31,newLogContext,experimental,@grafana/observability-logs,false,false,true
2025-07-31,newClickhouseConfigPageDesign,privatePreview,@grafana/partner-datasources,false,false,false
2025-07-31,teamFolders,experimental,@grafana/grafana-frontend-navigation,false,false,false
2025-08-01,restrictedPluginApis,experimental,@grafana/plugins-platform-backend,false,false,true
2025-08-01,favoriteDatasources,experimental,@grafana/plugins-platform-backend,false,false,true
2025-08-01,newLogContext,experimental,@grafana/observability-logs,false,false,true
2025-08-01,newClickhouseConfigPageDesign,privatePreview,@grafana/partner-datasources,false,false,false
2025-08-01,teamFolders,experimental,@grafana/grafana-frontend-navigation,false,false,false
2025-10-22,interactiveLearning,preview,@grafana/pathfinder,false,false,false
2025-07-31,alertingTriage,experimental,@grafana/alerting-squad,false,false,false
2026-02-12,alertingAlertsActivityBanner,experimental,@grafana/alerting-squad,false,false,true
2025-07-31,graphiteBackendMode,privatePreview,@grafana/partner-datasources,false,false,false
2025-07-31,azureResourcePickerUpdates,GA,@grafana/partner-datasources,false,false,true
2025-07-31,prometheusTypeMigration,experimental,@grafana/partner-datasources,false,true,false
2025-07-31,pluginContainers,privatePreview,@grafana/plugins-platform-backend,false,true,false
2025-08-01,alertingTriage,experimental,@grafana/alerting-squad,false,false,false
2026-02-18,alertingAlertsActivityBanner,experimental,@grafana/alerting-squad,false,false,true
2025-08-01,graphiteBackendMode,privatePreview,@grafana/partner-datasources,false,false,false
2025-08-01,azureResourcePickerUpdates,GA,@grafana/partner-datasources,false,false,true
2025-08-01,prometheusTypeMigration,experimental,@grafana/partner-datasources,false,true,false
2025-08-01,pluginContainers,privatePreview,@grafana/plugins-platform-backend,false,true,false
2025-08-29,cdnPluginsLoadFirst,experimental,@grafana/plugins-platform-backend,false,false,false
2025-08-29,cdnPluginsUrls,experimental,@grafana/plugins-platform-backend,false,false,false
2025-10-24,pluginInstallAPISync,experimental,@grafana/plugins-platform-backend,false,false,false
2025-10-20,newGauge,preview,@grafana/dataviz-squad,false,false,true
2025-11-12,newVizSuggestions,preview,@grafana/dataviz-squad,false,false,true
2026-01-28,panelStyleActions,experimental,@grafana/dataviz-squad,false,false,true
2026-02-09,vizPresets,preview,@grafana/dataviz-squad,false,false,true
2025-12-01,externalVizSuggestions,experimental,@grafana/dataviz-squad,false,false,true
2026-02-18,panelStyleActions,experimental,@grafana/dataviz-squad,false,false,true
2026-02-18,vizPresets,preview,@grafana/dataviz-squad,false,false,true
2025-12-02,externalVizSuggestions,experimental,@grafana/dataviz-squad,false,false,true
2025-12-18,heatmapRowsAxisOptions,experimental,@grafana/dataviz-squad,false,false,true
2025-10-17,preventPanelChromeOverflow,preview,@grafana/grafana-frontend-platform,false,false,true
2025-10-31,jaegerEnableGrpcEndpoint,experimental,@grafana/oss-big-tent,false,false,false
2025-10-17,pluginStoreServiceLoading,experimental,@grafana/plugins-platform-backend,false,false,false
2025-11-12,newPanelPadding,preview,@grafana/dashboards-squad,false,false,true
2025-10-20,onlyStoreActionSets,GA,@grafana/identity-access-team,false,false,false
2026-02-06,excludeRedundantManagedPermissions,experimental,@grafana/identity-access-team,false,false,false
2026-02-18,excludeRedundantManagedPermissions,experimental,@grafana/identity-access-team,false,false,false
2025-12-16,pluginInsights,experimental,@grafana/plugins-platform-backend,false,false,true
2025-10-29,panelTimeSettings,experimental,@grafana/dashboards-squad,false,false,false
2025-12-15,elasticsearchRawDSLQuery,experimental,@grafana/partner-datasources,false,false,false
@ -288,13 +289,13 @@ Created,Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
2026-01-06,secretsManagementAppPlatformAwsKeeper,experimental,@grafana/grafana-operator-experience-squad,false,false,false
2026-01-07,profilesExemplars,experimental,@grafana/observability-traces-and-profiling,false,false,false
2026-01-14,alertingSyncDispatchTimer,experimental,@grafana/alerting-squad,false,true,false
2026-01-15,queryWithAssistant,experimental,@grafana/oss-big-tent,false,false,true
2026-01-13,queryEditorNext,experimental,@grafana/datapro,false,false,true
2026-01-20,kubernetesTeamBindings,experimental,@grafana/identity-access-team,false,false,false
2026-02-04,kubernetesTeamsHandlerRedirect,experimental,@grafana/identity-access-team,false,false,false
2026-02-09,kubernetesTeamSync,experimental,@grafana/identity-access-team,false,false,false
2026-01-27,alertingMultiplePolicies,experimental,@grafana/alerting-squad,false,false,false
2026-02-11,alertingIgnorePendingForNoDataAndError,experimental,@grafana/alerting-squad,false,false,false
2026-02-11,alertingNotificationHistoryRuleViewer,experimental,@grafana/alerting-squad,false,false,false
2026-02-13,alertingNotificationHistoryGlobal,experimental,@grafana/alerting-squad,false,false,false
2026-02-18,queryWithAssistant,experimental,@grafana/oss-big-tent,false,false,true
2026-02-18,queryEditorNext,experimental,@grafana/datapro,false,false,true
2026-02-18,kubernetesTeamBindings,experimental,@grafana/identity-access-team,false,false,false
2026-02-18,kubernetesTeamsHandlerRedirect,experimental,@grafana/identity-access-team,false,false,false
2026-02-18,kubernetesTeamSync,experimental,@grafana/identity-access-team,false,false,false
2026-02-18,alertingMultiplePolicies,experimental,@grafana/alerting-squad,false,false,false
2026-02-18,alertingIgnorePendingForNoDataAndError,experimental,@grafana/alerting-squad,false,false,false
2026-02-18,alertingNotificationHistoryRuleViewer,experimental,@grafana/alerting-squad,false,false,false
2026-02-18,alertingNotificationHistoryGlobal,experimental,@grafana/alerting-squad,false,false,false
2026-02-18,react19,experimental,@grafana/grafana-frontend-platform,false,false,false

1 Created Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
13 2024-10-23 lokiShardSplitting experimental @grafana/observability-logs false false true
14 2023-02-09 lokiQuerySplitting GA @grafana/observability-logs false false true
15 2022-02-09 influxdbBackendMigration GA @grafana/partner-datasources false false true
16 2026-01-19 2026-02-18 liveAPIServer experimental @grafana/grafana-app-platform-squad true true false
17 2025-08-29 starsFromAPIServer experimental @grafana/grafana-frontend-navigation false false true
18 2025-08-29 kubernetesStars experimental @grafana/grafana-app-platform-squad false true false
19 2023-11-29 influxqlStreamingParser experimental @grafana/partner-datasources false false false
24 2023-06-06 refactorVariablesTimeRange preview @grafana/dashboards-squad false false false
25 2023-05-05 faroDatasourceSelector preview @grafana/app-o11y false false true
26 2023-04-24 enableDatagridEditing preview @grafana/dataviz-squad false false false
27 2026-02-10 2026-02-18 faroSessionReplay experimental @grafana/session-replay false false true
28 2023-07-12 logsExploreTableVisualisation GA @grafana/observability-logs false false true
29 2023-07-06 awsDatasourcesTempCredentials GA @grafana/aws-datasources false false false
30 2023-07-13 mlExpressions experimental @grafana/alerting-squad false false false
42 2023-09-19 lokiRunQueriesInParallel privatePreview @grafana/oss-big-tent false false false
43 2023-09-28 externalServiceAccounts preview @grafana/identity-access-team false false false
44 2023-12-05 kubernetesSnapshots experimental @grafana/grafana-app-platform-squad false true false
45 2025-06-25 2025-06-26 kubernetesLibraryPanels experimental @grafana/grafana-app-platform-squad false true false
46 2024-06-05 kubernetesDashboards GA @grafana/dashboards-squad false false false
47 2025-07-31 2025-08-01 kubernetesShortURLs experimental @grafana/grafana-app-platform-squad false true false
48 2025-08-29 useKubernetesShortURLsAPI experimental @grafana/sharing-squad false false true
49 2025-07-31 2025-08-01 kubernetesAlertingRules experimental @grafana/alerting-squad false true false
50 2025-08-29 kubernetesCorrelations experimental @grafana/datapro false true false
51 2025-12-09 kubernetesUnifiedStorageQuotas experimental @grafana/search-and-storage false true false
52 2025-10-16 kubernetesLogsDrilldown experimental @grafana/observability-logs false true false
56 2025-04-11 dashboardSchemaValidationLogging experimental @grafana/grafana-app-platform-squad false false false
57 2025-07-30 scanRowInvalidDashboardParseFallbackEnabled experimental @grafana/search-and-storage false false false
58 2024-05-23 datasourceQueryTypes experimental @grafana/grafana-app-platform-squad false true false
59 2026-01-22 2026-02-18 datasourceDisableIdApi experimental @grafana/grafana-datasources-core-services false true false
60 2024-04-19 queryService experimental @grafana/grafana-datasources-core-services false true false
61 2025-08-28 queryServiceWithConnections experimental @grafana/grafana-datasources-core-services false true false
62 2026-02-13 2026-02-18 useNewAPIsForDatasourceCRUD experimental @grafana/grafana-datasources-core-services false true true
63 2024-04-19 queryServiceRewrite experimental @grafana/grafana-datasources-core-services false true false
64 2024-04-19 queryServiceFromUI experimental @grafana/grafana-datasources-core-services false false true
65 2026-01-19 2026-02-18 datasourcesRerouteLegacyCRUDAPIs experimental @grafana/grafana-datasources-core-services false false false
66 2026-02-18 datasourcesApiServerEnableResourceEndpoint experimental @grafana/grafana-datasources-core-services false false false
67 2023-10-20 cloudWatchBatchQueries GA @grafana/aws-datasources false false false
68 2023-10-12 cachingOptimizeSerializationMemoryUsage experimental @grafana/grafana-operator-experience-squad false false false
69 2023-10-30 alertmanagerRemoteSecondary experimental @grafana/alerting-squad false false false
82 2025-12-08 perPanelFiltering experimental @grafana/dashboards-squad false false true
83 2023-11-03 panelFilterVariable experimental @grafana/dashboards-squad false false true
84 2023-11-06 pdfTables preview @grafana/grafana-operator-experience-squad false false false
85 2026-02-16 2026-02-18 reportingV2Layouts experimental @grafana/grafana-operator-experience-squad false false true
86 2024-01-02 canvasPanelPanZoom preview @grafana/dataviz-squad false false true
87 2025-07-24 timeComparison experimental @grafana/dataviz-squad false false true
88 2023-12-13 tableSharedCrosshair experimental @grafana/dataviz-squad false false true
90 2024-01-10 alertingQueryOptimization GA @grafana/alerting-squad false false false
91 2024-01-18 jitterAlertRulesWithinGroups preview @grafana/alerting-squad false true false
92 2025-12-29 auditLoggingAppPlatform experimental @grafana/grafana-operator-experience-squad false true false
93 2025-07-31 2025-08-01 secretsManagementAppPlatformUI experimental @grafana/grafana-operator-experience-squad false false false
94 2026-02-04 2026-02-18 secretsKeeperUI experimental @grafana/grafana-operator-experience-squad false false true
95 2024-01-23 alertingSaveStatePeriodic privatePreview @grafana/alerting-squad false false false
96 2025-01-27 alertingSaveStateCompressed preview @grafana/alerting-squad false false false
97 2024-11-27 scopeApi experimental @grafana/grafana-app-platform-squad false false false
112 2024-04-16 disableNumericMetricsSortingInExpressions experimental @grafana/oss-big-tent false true false
113 2024-04-22 grafanaManagedRecordingRules experimental @grafana/alerting-squad false false false
114 2022-10-07 queryLibrary preview @grafana/sharing-squad false false false
115 2026-01-19 2026-02-18 savedQueriesRBAC preview @grafana/sharing-squad false false false
116 2025-08-29 dashboardLibrary experimental @grafana/sharing-squad false false false
117 2025-11-07 suggestedDashboards experimental @grafana/sharing-squad false false false
118 2026-01-21 2026-02-18 dashboardValidatorApp experimental @grafana/sharing-squad false false false
119 2025-10-28 dashboardTemplates preview @grafana/sharing-squad false false false
120 2024-05-24 alertingListViewV2 privatePreview @grafana/alerting-squad false false true
121 2026-01-14 alertingNavigationV2 experimental @grafana/alerting-squad false false false
122 2025-12-19 alertingSavedSearches experimental @grafana/alerting-squad false false true
123 2024-05-23 alertingDisableSendAlertsExternal experimental @grafana/alerting-squad false false false
124 2026-02-06 2026-02-18 alertingSyncNotifiersApiMigration experimental @grafana/alerting-squad false false true
125 2024-05-27 preserveDashboardStateWhenNavigating experimental @grafana/dashboards-squad false false false
126 2024-05-29 alertingCentralAlertHistory experimental @grafana/alerting-squad false false false
127 2024-06-05 pluginProxyPreserveTrailingSlash GA @grafana/plugins-platform-backend false false false
136 2024-09-11 alertingFilterV2 experimental @grafana/alerting-squad false false false
137 2024-08-09 dataplaneAggregator experimental @grafana/grafana-app-platform-squad false true false
138 2024-08-30 newFiltersUI GA @grafana/dashboards-squad false false false
139 2025-07-31 2025-08-01 vizActionsAuth preview @grafana/dataviz-squad false false true
140 2024-09-27 alertingPrometheusRulesPrimary experimental @grafana/alerting-squad false false true
141 2024-08-29 exploreLogsShardSplitting experimental @grafana/observability-logs false false true
142 2024-08-29 exploreLogsAggregatedMetrics experimental @grafana/observability-logs false false true
165 2025-07-23 alertingAIFeedback experimental @grafana/alerting-squad false false false
166 2025-07-16 alertingAIImproveAlertRules experimental @grafana/alerting-squad false false false
167 2025-07-16 alertingAIGenTemplates experimental @grafana/alerting-squad false false false
168 2025-07-31 2025-08-01 alertingEnrichmentPerRule experimental @grafana/alerting-squad false false false
169 2025-08-29 alertingEnrichmentAssistantInvestigations experimental @grafana/alerting-squad false false false
170 2025-07-16 alertingAIAnalyzeCentralStateHistory experimental @grafana/alerting-squad false false false
171 2024-11-22 alertingNotificationsStepMode GA @grafana/alerting-squad false false true
175 2024-12-27 k8SFolderCounts experimental @grafana/search-and-storage false false false
176 2025-01-09 improvedExternalSessionHandlingSAML GA @grafana/identity-access-team false false false
177 2025-05-22 teamHttpHeadersTempo experimental @grafana/identity-access-team false false false
178 2026-02-05 2026-02-18 teamHttpHeadersFromAppPlatform experimental @grafana/identity-access-team false false false
179 2025-01-20 grafanaAdvisor privatePreview @grafana/plugins-platform-backend false false false
180 2025-01-15 elasticsearchImprovedParsing experimental @grafana/partner-datasources false false false
181 2025-01-21 datasourceConnectionsTab privatePreview @grafana/plugins-platform-backend false false true
195 2025-03-14 infinityRunQueriesInParallel privatePreview @grafana/oss-big-tent false false false
196 2025-03-14 alertingMigrationUI GA @grafana/alerting-squad false false true
197 2025-05-21 alertingImportYAMLUI GA @grafana/alerting-squad false false true
198 2026-01-27 2026-02-18 alertingMigrationWizardUI experimental @grafana/alerting-squad false false true
199 2025-04-02 azureMonitorLogsBuilderEditor preview @grafana/partner-datasources false false false
200 2025-03-31 localeFormatPreference deprecated @grafana/grafana-frontend-platform false false false
201 2025-03-21 unifiedStorageGrpcConnectionPool experimental @grafana/search-and-storage false false false
205 2025-04-09 unifiedNavbars GA @grafana/plugins-platform-backend false false true
206 2025-04-07 logsPanelControls preview @grafana/observability-logs false false true
207 2025-04-09 metricsFromProfiles experimental @grafana/observability-traces-and-profiling false false true
208 2025-07-31 2025-08-01 grafanaAssistantInProfilesDrilldown GA @grafana/observability-traces-and-profiling false false true
209 2025-07-15 tempoAlerting experimental @grafana/observability-traces-and-profiling false false false
210 2025-04-16 pluginsAutoUpdate experimental @grafana/plugins-platform-backend false false false
211 2025-04-22 alertingListViewV2PreviewToggle privatePreview @grafana/alerting-squad false false true
213 2025-04-24 alertingBulkActionsInUI GA @grafana/alerting-squad false false true
214 2025-06-18 kubernetesAuthzApis deprecated @grafana/identity-access-team false false false
215 2025-08-29 kubernetesAuthZHandlerRedirect deprecated @grafana/identity-access-team false false false
216 2026-02-04 2026-02-18 kubernetesAuthZResourcePermissionsRedirect experimental @grafana/identity-access-team false false false
217 2026-02-04 2026-02-18 kubernetesAuthZRolesRedirect experimental @grafana/identity-access-team false false false
218 2025-07-31 2025-08-01 kubernetesAuthzResourcePermissionApis experimental @grafana/identity-access-team false false false
219 2025-10-13 kubernetesAuthzZanzanaSync experimental @grafana/identity-access-team false false false
220 2026-01-12 kubernetesAuthzCoreRolesApi experimental @grafana/identity-access-team false false false
221 2026-01-15 kubernetesAuthzGlobalRolesApi experimental @grafana/identity-access-team false false false
222 2026-01-12 kubernetesAuthzRolesApi experimental @grafana/identity-access-team false false false
223 2026-01-14 2026-02-18 kubernetesAuthzTeamLBACRuleApi experimental @grafana/identity-access-team false false false
224 2026-01-12 kubernetesAuthzRoleBindingsApi experimental @grafana/identity-access-team false false false
225 2025-07-25 kubernetesAuthnMutation experimental @grafana/identity-access-team false false false
226 2025-11-25 kubernetesExternalGroupMapping experimental @grafana/identity-access-team false false false
228 2025-12-10 recentlyViewedDashboards experimental @grafana/grafana-frontend-navigation false false true
229 2026-01-13 experimentRecentlyViewedDashboards experimental @grafana/grafana-frontend-navigation false false true
230 2025-06-06 alertEnrichment experimental @grafana/alerting-squad false false false
231 2025-07-31 2025-08-01 alertEnrichmentMultiStep experimental @grafana/alerting-squad false false false
232 2025-07-31 2025-08-01 alertEnrichmentConditional experimental @grafana/alerting-squad false false false
233 2025-06-10 alertingImportAlertmanagerAPI experimental @grafana/alerting-squad false false false
234 2025-07-31 2025-08-01 alertingImportAlertmanagerUI experimental @grafana/alerting-squad false false false
235 2026-01-26 2026-02-18 alertingDisableDMAinUI experimental @grafana/alerting-squad false false false
236 2025-07-15 sharingDashboardImage GA @grafana/sharing-squad false false true
237 2025-06-17 preferLibraryPanelTitle privatePreview @grafana/dashboards-squad false false false
238 2025-06-24 tabularNumbers GA @grafana/grafana-frontend-platform false false false
245 2025-07-18 unifiedStorageSearchDualReaderEnabled experimental @grafana/search-and-storage false false false
246 2025-07-31 dashboardLevelTimeMacros experimental @grafana/dashboards-squad false false true
247 2025-07-25 alertmanagerRemoteSecondaryWithRemoteState experimental @grafana/alerting-squad false false false
248 2025-07-31 2025-08-01 restrictedPluginApis experimental @grafana/plugins-platform-backend false false true
249 2025-07-31 2025-08-01 favoriteDatasources experimental @grafana/plugins-platform-backend false false true
250 2025-07-31 2025-08-01 newLogContext experimental @grafana/observability-logs false false true
251 2025-07-31 2025-08-01 newClickhouseConfigPageDesign privatePreview @grafana/partner-datasources false false false
252 2025-07-31 2025-08-01 teamFolders experimental @grafana/grafana-frontend-navigation false false false
253 2025-10-22 interactiveLearning preview @grafana/pathfinder false false false
254 2025-07-31 2025-08-01 alertingTriage experimental @grafana/alerting-squad false false false
255 2026-02-12 2026-02-18 alertingAlertsActivityBanner experimental @grafana/alerting-squad false false true
256 2025-07-31 2025-08-01 graphiteBackendMode privatePreview @grafana/partner-datasources false false false
257 2025-07-31 2025-08-01 azureResourcePickerUpdates GA @grafana/partner-datasources false false true
258 2025-07-31 2025-08-01 prometheusTypeMigration experimental @grafana/partner-datasources false true false
259 2025-07-31 2025-08-01 pluginContainers privatePreview @grafana/plugins-platform-backend false true false
260 2025-08-29 cdnPluginsLoadFirst experimental @grafana/plugins-platform-backend false false false
261 2025-08-29 cdnPluginsUrls experimental @grafana/plugins-platform-backend false false false
262 2025-10-24 pluginInstallAPISync experimental @grafana/plugins-platform-backend false false false
263 2025-10-20 newGauge preview @grafana/dataviz-squad false false true
264 2025-11-12 newVizSuggestions preview @grafana/dataviz-squad false false true
265 2026-01-28 2026-02-18 panelStyleActions experimental @grafana/dataviz-squad false false true
266 2026-02-09 2026-02-18 vizPresets preview @grafana/dataviz-squad false false true
267 2025-12-01 2025-12-02 externalVizSuggestions experimental @grafana/dataviz-squad false false true
268 2025-12-18 heatmapRowsAxisOptions experimental @grafana/dataviz-squad false false true
269 2025-10-17 preventPanelChromeOverflow preview @grafana/grafana-frontend-platform false false true
270 2025-10-31 jaegerEnableGrpcEndpoint experimental @grafana/oss-big-tent false false false
271 2025-10-17 pluginStoreServiceLoading experimental @grafana/plugins-platform-backend false false false
272 2025-11-12 newPanelPadding preview @grafana/dashboards-squad false false true
273 2025-10-20 onlyStoreActionSets GA @grafana/identity-access-team false false false
274 2026-02-06 2026-02-18 excludeRedundantManagedPermissions experimental @grafana/identity-access-team false false false
275 2025-12-16 pluginInsights experimental @grafana/plugins-platform-backend false false true
276 2025-10-29 panelTimeSettings experimental @grafana/dashboards-squad false false false
277 2025-12-15 elasticsearchRawDSLQuery experimental @grafana/partner-datasources false false false
289 2026-01-06 secretsManagementAppPlatformAwsKeeper experimental @grafana/grafana-operator-experience-squad false false false
290 2026-01-07 profilesExemplars experimental @grafana/observability-traces-and-profiling false false false
291 2026-01-14 alertingSyncDispatchTimer experimental @grafana/alerting-squad false true false
292 2026-01-15 2026-02-18 queryWithAssistant experimental @grafana/oss-big-tent false false true
293 2026-01-13 2026-02-18 queryEditorNext experimental @grafana/datapro false false true
294 2026-01-20 2026-02-18 kubernetesTeamBindings experimental @grafana/identity-access-team false false false
295 2026-02-04 2026-02-18 kubernetesTeamsHandlerRedirect experimental @grafana/identity-access-team false false false
296 2026-02-09 2026-02-18 kubernetesTeamSync experimental @grafana/identity-access-team false false false
297 2026-01-27 2026-02-18 alertingMultiplePolicies experimental @grafana/alerting-squad false false false
298 2026-02-11 2026-02-18 alertingIgnorePendingForNoDataAndError experimental @grafana/alerting-squad false false false
299 2026-02-11 2026-02-18 alertingNotificationHistoryRuleViewer experimental @grafana/alerting-squad false false false
300 2026-02-13 2026-02-18 alertingNotificationHistoryGlobal experimental @grafana/alerting-squad false false false
301 2026-02-18 react19 experimental @grafana/grafana-frontend-platform false false false

View file

@ -203,6 +203,10 @@ const (
// Handle datasource CRUD requests to the legacy API routes by querying the new datasource api group endpoints behind the scenes.
FlagDatasourcesRerouteLegacyCRUDAPIs = "datasourcesRerouteLegacyCRUDAPIs"
// FlagDatasourcesApiServerEnableResourceEndpoint
// Handle datasource resource requests to the legacy API routes by querying the new datasource api group endpoints behind the scenes.
FlagDatasourcesApiServerEnableResourceEndpoint = "datasourcesApiServerEnableResourceEndpoint"
// FlagCloudWatchBatchQueries
// Runs CloudWatch metrics queries as separate batches
FlagCloudWatchBatchQueries = "cloudWatchBatchQueries"

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,284 @@
package datasource
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"testing"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/tests/apis"
"github.com/grafana/grafana/pkg/tests/testinfra"
"github.com/grafana/grafana/pkg/util/testutil"
)
// testdataResponse represents the JSON response from /test/json endpoint for grafana-test-dataosurce
type testdataResponse struct {
Message string `json:"message"`
Request struct {
Method string `json:"method"`
URL url.URL `json:"url"`
Headers map[string][]string `json:"headers"`
Body map[string]any `json:"body"`
} `json:"request"`
}
func setup(t *testing.T) *apis.K8sTestHelper {
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
EnableFeatureToggles: []string{
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, // Required to start the datasource api servers
featuremgmt.FlagQueryServiceWithConnections, // enables CRUD endpoints
featuremgmt.FlagDatasourcesApiServerEnableResourceEndpoint, // enables resource endpoint
},
})
t.Cleanup(helper.Shutdown)
ctx := context.Background()
client := helper.Org1.Admin.ResourceClient(t, schema.GroupVersionResource{
Group: "grafana-testdata-datasource.datasource.grafana.app",
Version: "v0alpha1",
Resource: "datasources",
}).Namespace("default")
_, err := client.Create(ctx, &unstructured.Unstructured{
Object: map[string]any{
"apiVersion": "grafana-testdata-datasource.datasource.grafana.app/v0alpha1",
"kind": "DataSource",
"metadata": map[string]any{
"name": "test-resource",
},
"spec": map[string]any{
"title": "Test Resource Datasource",
},
},
}, metav1.CreateOptions{})
if err != nil {
t.Fatalf("failed to create datasource: %v", err)
}
return helper
}
func TestIntegrationDatasourceResources(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
helper := setup(t)
t.Run("GET resource endpoint returns testdata response", func(t *testing.T) {
raw := apis.DoRequest[any](helper, apis.RequestParams{
User: helper.Org1.Admin,
Method: http.MethodGet,
Path: "/apis/grafana-testdata-datasource.datasource.grafana.app/v0alpha1/namespaces/default/datasources/test-resource/resource",
}, nil)
require.Equal(t, http.StatusOK, raw.Response.StatusCode, "expected OK status, got body: %s", string(raw.Body))
require.Contains(t, string(raw.Body), "Hello world", "expected testdata greeting response")
})
t.Run("GET /test/json returns JSON with request echo", func(t *testing.T) {
helper := setup(t)
raw := apis.DoRequest[testdataResponse](helper, apis.RequestParams{
User: helper.Org1.Admin,
Method: http.MethodGet,
Path: "/apis/grafana-testdata-datasource.datasource.grafana.app/v0alpha1/namespaces/default/datasources/test-resource/resource/test/json",
}, &testdataResponse{})
require.Equal(t, http.StatusOK, raw.Response.StatusCode)
require.NotNil(t, raw.Result)
require.Equal(t, "Hello world from test datasource!", raw.Result.Message)
require.Equal(t, "GET", raw.Result.Request.Method)
})
t.Run("POST with body echoes request body", func(t *testing.T) {
raw := apis.DoRequest[testdataResponse](helper, apis.RequestParams{
User: helper.Org1.Admin,
Method: http.MethodPost,
Path: "/apis/grafana-testdata-datasource.datasource.grafana.app/v0alpha1/namespaces/default/datasources/test-resource/resource/test/json",
Body: []byte(`{"foo": "bar", "count": 42}`),
}, &testdataResponse{})
require.Equal(t, http.StatusOK, raw.Response.StatusCode)
require.NotNil(t, raw.Result)
require.Equal(t, "POST", raw.Result.Request.Method)
require.NotNil(t, raw.Result.Request.Body)
require.Equal(t, "bar", raw.Result.Request.Body["foo"])
require.Equal(t, float64(42), raw.Result.Request.Body["count"])
})
}
func TestIntegrationDatasourceResourceHeaders(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
helper := setup(t)
t.Run("auth headers are stripped before request reaches plugin", func(t *testing.T) {
raw := apis.DoRequest[testdataResponse](helper, apis.RequestParams{
User: helper.Org1.Admin,
Method: http.MethodPost,
Path: "/apis/grafana-testdata-datasource.datasource.grafana.app/v0alpha1/namespaces/default/datasources/test-resource/resource/test/json",
Headers: map[string]string{
"X-Custom-Header": "custom-value",
},
ContentType: "application/json",
Accept: "application/json",
Body: []byte(`{"foo": "bar", "count": 42}`),
}, &testdataResponse{})
require.Equal(t, http.StatusOK, raw.Response.StatusCode)
require.NotNil(t, raw.Result)
// ClearAuthHeaders middleware should strip auth headers before request is sent to plugin
authHeaders := raw.Result.Request.Headers["Authorization"]
require.Empty(t, authHeaders, "Authorization header should be stripped before request reaches plugin")
deviceIDHeaders := raw.Result.Request.Headers["X-Grafana-Device-Id"]
require.Empty(t, deviceIDHeaders, "X-Grafana-Device-Id header should be stripped before request reaches plugin")
// Non-auth headers must still be forwarded
customHeaders := raw.Result.Request.Headers["X-Custom-Header"]
require.NotEmpty(t, customHeaders)
require.Equal(t, "custom-value", customHeaders[0])
})
t.Run("query parameters are forwarded to plugin", func(t *testing.T) {
raw := apis.DoRequest[testdataResponse](helper, apis.RequestParams{
User: helper.Org1.Admin,
Method: http.MethodGet,
Path: "/apis/grafana-testdata-datasource.datasource.grafana.app/v0alpha1/namespaces/default/datasources/test-resource/resource/test/json?param1=value1&param2=value2",
}, &testdataResponse{})
require.Equal(t, http.StatusOK, raw.Response.StatusCode)
require.NotNil(t, raw.Result)
// parse raw.Body to testdataResponse
testdataResponse := &testdataResponse{}
err := json.Unmarshal(raw.Body, testdataResponse)
require.NoError(t, err, "failed to unmarshal response body: %s", string(raw.Body))
require.Contains(t, testdataResponse.Request.URL.RawQuery, "param1=value1", "URL in plugin should contain query parameters")
require.Contains(t, testdataResponse.Request.URL.RawQuery, "param2=value2", "URL in plugin should contain query parameters")
})
}
func TestIntegrationDatasourceStreamingResource(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
helper := setup(t)
t.Run("GET streaming resource returns expected response", func(t *testing.T) {
raw := apis.DoRequest[any](helper, apis.RequestParams{
User: helper.Org1.Admin,
Method: http.MethodGet,
Path: "/apis/grafana-testdata-datasource.datasource.grafana.app/v0alpha1/namespaces/default/datasources/test-resource/resource/test/stream",
}, nil)
require.NotNil(t, raw.Response.StatusCode)
require.Equal(t, int(http.StatusOK), raw.Response.StatusCode)
require.Contains(t, string(raw.Body), "Hello world from test datasource!")
})
}
func TestIntegrationDatasourceResourceAuthorization(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
helper := setup(t)
t.Run("resource endpoint returns 404 for non-existent datasource", func(t *testing.T) {
raw := apis.DoRequest[any](helper, apis.RequestParams{
User: helper.Org1.Admin,
Method: http.MethodGet,
Path: "/apis/grafana-testdata-datasource.datasource.grafana.app/v0alpha1/namespaces/default/datasources/does-not-exist/resource",
}, nil)
require.NotNil(t, raw.Status)
require.Equal(t, int32(http.StatusNotFound), raw.Status.Code)
})
t.Run("resource endpoint requires authentication", func(t *testing.T) {
// None role should not be able to access resource endpoint for GET
raw := apis.DoRequest[any](helper, apis.RequestParams{
User: helper.Org1.None,
Method: http.MethodGet,
Path: "/apis/grafana-testdata-datasource.datasource.grafana.app/v0alpha1/namespaces/default/datasources/test-resource/resource",
}, nil)
require.NotNil(t, raw.Response)
require.Equal(t, http.StatusForbidden, raw.Response.StatusCode)
})
t.Run("resource endpoint cross-org access denied", func(t *testing.T) {
// OrgB user should not be able to access Org1's datasource resources
raw := apis.DoRequest[any](helper, apis.RequestParams{
User: helper.OrgB.Admin,
Method: http.MethodGet,
Path: "/apis/grafana-testdata-datasource.datasource.grafana.app/v0alpha1/namespaces/default/datasources/test-resource/resource",
}, nil)
require.NotNil(t, raw.Status)
require.Equal(t, int32(http.StatusForbidden), raw.Status.Code)
})
}
func TestIntegrationDatasourceResourcesMethods(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
helper := setup(t)
httpMethods := []string{
http.MethodGet,
http.MethodPost,
http.MethodPut,
http.MethodPatch,
http.MethodDelete,
}
for _, method := range httpMethods {
t.Run(fmt.Sprintf("%s method is forwarded correctly", method), func(t *testing.T) {
var body []byte
if method == http.MethodPost || method == http.MethodPut || method == http.MethodPatch {
body = []byte(`{"test": "data"}`)
}
raw := apis.DoRequest[testdataResponse](helper, apis.RequestParams{
User: helper.Org1.Admin,
Method: method,
Path: "/apis/grafana-testdata-datasource.datasource.grafana.app/v0alpha1/namespaces/default/datasources/test-resource/resource/test/json",
Body: body,
}, &testdataResponse{})
require.Equal(t, http.StatusOK, raw.Response.StatusCode,
"method %s should return OK, got: %s", method, string(raw.Body))
require.NotNil(t, raw.Result)
require.Equal(t, method, raw.Result.Request.Method,
"echoed method should match request method")
})
}
}
func TestIntegrationDatasourceResourcesScenarios(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
helper := setup(t)
t.Run("GET /scenarios returns list of available scenarios", func(t *testing.T) {
raw := apis.DoRequest[[]map[string]any](helper, apis.RequestParams{
User: helper.Org1.Admin,
Method: http.MethodGet,
Path: "/apis/grafana-testdata-datasource.datasource.grafana.app/v0alpha1/namespaces/default/datasources/test-resource/resource/scenarios",
}, &[]map[string]any{})
require.Equal(t, http.StatusOK, raw.Response.StatusCode)
require.NotNil(t, raw.Result)
// Should return a list of scenarios
scenarios := *raw.Result
require.NotEmpty(t, scenarios, "should return at least one scenario")
for _, scenario := range scenarios {
require.NotEmpty(t, scenario["id"], "scenario should have id")
require.NotEmpty(t, scenario["name"], "scenario should have name")
}
})
}

View file

@ -40,8 +40,9 @@ func TestIntegrationTestDatasource(t *testing.T) {
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
EnableFeatureToggles: []string{
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, // Required to start the datasource api servers
featuremgmt.FlagQueryServiceWithConnections, // enables CRUD endpoints
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, // Required to start the datasource api servers
featuremgmt.FlagQueryServiceWithConnections, // enables CRUD endpoints
featuremgmt.FlagDatasourcesApiServerEnableResourceEndpoint, // enables resource endpoint
},
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
"datasources.grafana-testdata-datasource.datasource.grafana.app": {
@ -198,11 +199,8 @@ func TestIntegrationTestDatasource(t *testing.T) {
Method: "GET",
Path: "/apis/grafana-testdata-datasource.datasource.grafana.app/v0alpha1/namespaces/default/datasources/test/resource",
}, nil)
// endpoint is disabled currently because it has not been
// sufficiently tested.
// for more info see pkg/registry/apis/datasource/sub_resource.go
require.Equal(t, int32(501), raw.Status.Code)
// require.Equal(t, `Hello world from test datasource!`, string(raw.Body))
require.Equal(t, http.StatusOK, raw.Response.StatusCode)
require.Contains(t, string(raw.Body), "Hello world from test datasource!")
})
t.Run("delete", func(t *testing.T) {
@ -222,8 +220,9 @@ func TestIntegrationTestDatasourceAccess(t *testing.T) {
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
EnableFeatureToggles: []string{
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, // Required to start the datasource api servers
featuremgmt.FlagQueryServiceWithConnections, // enables CRUD endpoints
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, // Required to start the datasource api servers
featuremgmt.FlagQueryServiceWithConnections, // enables CRUD endpoints
featuremgmt.FlagDatasourcesApiServerEnableResourceEndpoint, // enables resource endpoint
},
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
"datasources.grafana-testdata-datasource.datasource.grafana.app": {

View file

@ -466,6 +466,7 @@ type RequestParams struct {
Body []byte
ContentType string
Accept string
Headers map[string]string
}
type K8sResponse[T any] struct {
@ -571,6 +572,9 @@ func DoRequest[T any](c *K8sTestHelper, params RequestParams, result *T) K8sResp
if params.Accept != "" {
req.Header.Set("Accept", params.Accept)
}
for k, v := range params.Headers {
req.Header.Set(k, v)
}
rsp, err := sharedHTTPClient.Do(req)
require.NoError(c.t, err)

View file

@ -34,6 +34,7 @@ func TestIntegrationOpenAPIs(t *testing.T) {
featuremgmt.FlagKubernetesAlertingRules,
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, // library panels in v0
featuremgmt.FlagQueryServiceWithConnections,
featuremgmt.FlagDatasourcesApiServerEnableResourceEndpoint,
featuremgmt.FlagKubernetesShortURLs,
featuremgmt.FlagKubernetesCorrelations,
featuremgmt.FlagKubernetesAlertingHistorian,