Prometheus: Remove from core plugins (#129332)

* remove prometheus frontend

* remove prometheus backend

* remove prometheus from more places

* remove prometheus datasource from backend

* remove from more places

* mroe removal

* move logos

* fix proxy_test

* fix unit tests maybe

* prettier

* remove

* update with alertlist

* update tests
This commit is contained in:
ismail simsek
2026-07-29 12:16:22 +02:00
committed by GitHub
parent 1262e5db6a
commit fbdd82fde3
43 changed files with 111 additions and 1170 deletions
-4
View File
@@ -357,9 +357,6 @@
/pkg/tsdb/cloudwatch/ @grafana/data-sources-plugins
/pkg/tsdb/azuremonitor/ @grafana/data-sources-plugins
# Observability backend code
/pkg/tsdb/prometheus/ @grafana/data-sources-plugins
# OSS Big Tent backend code
/pkg/tsdb/mysql/ @grafana/data-sources-plugins
/pkg/tsdb/sqlmacro/ @grafana/data-sources-plugins
@@ -1156,7 +1153,6 @@ eslint-suppressions.json @grafanabot
/public/app/plugins/datasource/influxdb/ @grafana/data-sources-plugins
/public/app/plugins/datasource/mixed/ @grafana/dashboards-squad
/public/app/plugins/datasource/mysql/ @grafana/data-sources-plugins
/public/app/plugins/datasource/prometheus/ @grafana/data-sources-plugins
/public/app/plugins/datasource/alertmanager/ @grafana/alerting-squad
# Grafana Sharing Squad
-8
View File
@@ -91,14 +91,6 @@
"url": "https://github.com/orgs/grafana/projects/190"
}
},
{
"type": "label",
"name": "datasource/Prometheus",
"action": "addToProject",
"addToProject": {
"url": "https://github.com/orgs/grafana/projects/457"
}
},
{
"type": "label",
"name": "datasource/InfluxDB",
-9
View File
@@ -116,15 +116,6 @@
"action": "updateLabel",
"addLabel": "datasource/MySQL"
},
{
"type": "changedfiles",
"matches": [
"public/app/plugins/datasource/prometheus/**/*",
"pkg/tsdb/prometheus/**/*"
],
"action": "updateLabel",
"addLabel": "datasource/Prometheus"
},
{
"type": "changedfiles",
"matches": [
@@ -108,8 +108,6 @@ datasource/MSSQL
datasource/MySQL
datasource/OpenSearch
datasource/Phlare
datasource/Postgres
datasource/Prometheus
datasource/SiteWIse
datasource/Splunk
datasource/TestDataDB
@@ -4,15 +4,12 @@ on:
pull_request:
types: [opened]
paths:
- "packages/grafana-prometheus/**"
- "public/app/plugins/datasource/cloudwatch/**"
- "public/app/plugins/datasource/graphite/**"
- "public/app/plugins/datasource/influxdb/**"
- "public/app/plugins/datasource/prometheus/**"
- "pkg/tsdb/cloudwatch/**"
- "pkg/tsdb/graphite/**"
- "pkg/tsdb/influxdb/**"
- "pkg/tsdb/prometheus/**"
jobs:
add-comment:
+1 -1
View File
@@ -137,7 +137,7 @@ func TestCoreProvider_loadPlugins(t *testing.T) {
provider := NewCoreProviderWithTTL(&logging.NoOpLogger{}, staticRootPath, false, defaultCoreTTL)
err = provider.loadPlugins(ctx)
require.NoError(t, err)
assert.Len(t, provider.loadedPlugins, 41)
assert.Len(t, provider.loadedPlugins, 40)
})
t.Run("loads plugins from staticRootPath", func(t *testing.T) {
-4
View File
@@ -237,10 +237,6 @@ require (
// Internal module references (every entry should also have a replace in the section below)
require (
// This needs to be here for other projects that import grafana/grafana
// For local development grafana/grafana will always use the local files
// Check go.work file for details
github.com/grafana/grafana-prometheus-datasource/pkg/promlib v0.0.11 // @grafana/data-sources-plugins
github.com/grafana/grafana/apps/advisor v0.0.0 // @grafana/grafana-catalog
github.com/grafana/grafana/apps/alerting/alertenrichment v0.0.0 // @grafana/alerting-backend
github.com/grafana/grafana/apps/alerting/notifications v0.0.0 // @grafana/alerting-backend
-2
View File
@@ -1620,8 +1620,6 @@ github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79
github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79/go.mod h1:wc6Hbh3K2TgCUSfBC/BOzabItujtHMESZeFk5ZhdxhQ=
github.com/grafana/grafana-plugin-sdk-go v0.292.2 h1:6xrNZpe905vzX1pY33Fyet7Oxw5xULcAHm2b8dvNZNY=
github.com/grafana/grafana-plugin-sdk-go v0.292.2/go.mod h1:MPviZeixuZDc5T3f62FBCCPSOJ51/JocZH5+ZiQ1DmM=
github.com/grafana/grafana-prometheus-datasource/pkg/promlib v0.0.11 h1:dN402Jd/FsSi6lvfCJR5lXiv7j2ucbPV/ZJCjOX4+zc=
github.com/grafana/grafana-prometheus-datasource/pkg/promlib v0.0.11/go.mod h1:rmYghozhFPZ5p+o8erXoCQ3BuSVDOuT5Y8dXWBUIXjs=
github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000 h1:/5LKSYgLmAhwA4m6iGUD4w1YkydEWWjazn9qxCFT8W0=
github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000/go.mod h1:/ZklAgE1i4f3Z8uriXwESmCr1VLF8lBGaJspuaGuf78=
github.com/grafana/memberlist v0.3.1-0.20251126142931-6f9f62ab6f86 h1:aTwfQuroOmOr//QEn9J1MtC4R4CPR9/IbUd8hZrbWKo=
@@ -2,11 +2,11 @@ import { clockPanelMetaCDN, v0alpha1Response } from '../test-fixtures/v0alpha1Re
import { isCorePlugin, isDecoupledCorePlugin } from './shared';
const prometheusResponse = v0alpha1Response.items.find((i) => i.spec.pluginJson.id === 'prometheus')!;
const alertListResponse = v0alpha1Response.items.find((i) => i.spec.pluginJson.id === 'alertlist')!;
describe('isCorePlugin', () => {
it('should return true for core plugins', () => {
expect(isCorePlugin(prometheusResponse.spec)).toBe(true);
expect(isCorePlugin(alertListResponse.spec)).toBe(true);
});
it('should return false for non core plugins', () => {
@@ -16,7 +16,7 @@ describe('isCorePlugin', () => {
describe('isDecoupledCorePlugin', () => {
it('should return false for core plugins that are not decoupled', () => {
expect(isDecoupledCorePlugin(prometheusResponse.spec)).toBe(false);
expect(isDecoupledCorePlugin(alertListResponse.spec)).toBe(false);
});
it('should return false for non core plugins', () => {
@@ -77,7 +77,7 @@ describe('v0alpha1AppMapper', () => {
it('should only map specs with type app', () => {
const result = v0alpha1AppMapper(v0alpha1Response);
expect(v0alpha1Response.items).toHaveLength(44);
expect(v0alpha1Response.items).toHaveLength(43);
expect(Object.keys(result)).toHaveLength(4);
expect(Object.keys(result)).toEqual(Object.keys(apps));
});
@@ -134,7 +134,7 @@ describe('v0alpha1PanelMapper', () => {
it('should only map specs with type panel', () => {
const result = v0alpha1PanelMapper(v0alpha1Response);
expect(v0alpha1Response.items).toHaveLength(44);
expect(v0alpha1Response.items).toHaveLength(43);
expect(Object.keys(result)).toHaveLength(28);
expect(Object.keys(result)).toEqual(Object.keys(panels));
});
@@ -159,7 +159,7 @@ describe('v0alpha1PanelMapper', () => {
});
const azureMeta = v0alpha1Response.items.find((i) => i.spec.pluginJson.id === 'grafana-azure-monitor-datasource')!;
const promMeta = v0alpha1Response.items.find((i) => i.spec.pluginJson.id === 'prometheus')!;
const promMeta = v0alpha1Response.items.find((i) => i.spec.pluginJson.id === 'dashboard')!;
describe('coreSpecMapper when called with a core plugin on cloud', () => {
beforeEach(() => {
@@ -206,14 +206,14 @@ describe('coreSpecMapper when called with a core plugin on cloud', () => {
const result = coreSpecMapper(promMeta.spec);
expect(result.baseUrl).toBe(
'https://grafana-assets.grafana-dev.net/grafana/13.0.0-24045599351/public/app/plugins/datasource/prometheus'
'https://grafana-assets.grafana-dev.net/grafana/13.0.0-24045599351/public/app/plugins/datasource/dashboard'
);
expect(result.module).toBe('core:plugin/prometheus');
expect(result.module).toBe('core:plugin/dashboard');
expect(result.info.logos.large).toBe(
'https://grafana-assets.grafana-dev.net/grafana/13.0.0-24045599351/public/app/plugins/datasource/prometheus/img/prometheus_logo.svg'
'https://grafana-assets.grafana-dev.net/grafana/13.0.0-24045599351/public/app/plugins/datasource/dashboard/dist/img/icn-reusequeries.svg'
);
expect(result.info.logos.small).toBe(
'https://grafana-assets.grafana-dev.net/grafana/13.0.0-24045599351/public/app/plugins/datasource/prometheus/img/prometheus_logo.svg'
'https://grafana-assets.grafana-dev.net/grafana/13.0.0-24045599351/public/app/plugins/datasource/dashboard/dist/img/icn-reusequeries.svg'
);
expect(result.info.screenshots).toEqual([]);
});
@@ -258,10 +258,10 @@ describe('coreSpecMapper when called with a core plugin on prem', () => {
it('should resolve correct urls for a non decoupled plugin', () => {
const result = coreSpecMapper(promMeta.spec);
expect(result.baseUrl).toBe('public/app/plugins/datasource/prometheus');
expect(result.module).toBe('core:plugin/prometheus');
expect(result.info.logos.large).toBe('public/app/plugins/datasource/prometheus/img/prometheus_logo.svg');
expect(result.info.logos.small).toBe('public/app/plugins/datasource/prometheus/img/prometheus_logo.svg');
expect(result.baseUrl).toBe('public/app/plugins/datasource/dashboard');
expect(result.module).toBe('core:plugin/dashboard');
expect(result.info.logos.large).toBe('public/app/plugins/datasource/dashboard/dist/img/icn-reusequeries.svg');
expect(result.info.logos.small).toBe('public/app/plugins/datasource/dashboard/dist/img/icn-reusequeries.svg');
expect(result.info.screenshots).toEqual([]);
});
});
@@ -3044,157 +3044,6 @@ export const v0alpha1Response: PluginMetasResponse = structuredClone({
},
status: {},
},
{
kind: 'Meta',
apiVersion: 'plugins.grafana.app/v0alpha1',
metadata: {
name: 'prometheus',
namespace: 'default',
},
spec: {
pluginJson: {
id: 'prometheus',
type: 'datasource',
name: 'Prometheus',
info: {
keywords: [],
logos: {
small: 'app/plugins/datasource/prometheus/img/prometheus_logo.svg',
large: 'app/plugins/datasource/prometheus/img/prometheus_logo.svg',
},
updated: '',
version: '',
author: {
name: 'Grafana Labs',
url: 'https://grafana.com',
},
description: 'Open source time series database & alerting',
links: [
{
name: 'Learn more',
url: 'https://prometheus.io/',
},
{
name: 'Raise issue',
url: 'https://github.com/grafana/grafana/issues/new',
},
{
name: 'Documentation',
url: 'https://grafana.com/docs/grafana/latest/datasources/prometheus/',
},
],
},
dependencies: {
grafanaDependency: '',
grafanaVersion: '*',
},
alerting: true,
annotations: true,
backend: true,
category: 'tsdb',
includes: [
{
type: 'dashboard',
name: 'Prometheus Stats',
role: 'Viewer',
path: 'dashboards/prometheus_stats.json',
},
{
type: 'dashboard',
name: 'Prometheus 2.0 Stats',
role: 'Viewer',
path: 'dashboards/prometheus_2_stats.json',
},
{
type: 'dashboard',
name: 'Grafana Stats',
role: 'Viewer',
path: 'dashboards/grafana_stats.json',
},
],
metrics: true,
multiValueFilterOperators: true,
queryOptions: {
minInterval: true,
},
routes: [
{
path: 'api/v1/query',
method: 'POST',
reqRole: 'Viewer',
reqAction: 'datasources:query',
},
{
path: 'api/v1/query_range',
method: 'POST',
reqRole: 'Viewer',
reqAction: 'datasources:query',
},
{
path: 'api/v1/series',
method: 'POST',
reqRole: 'Viewer',
reqAction: 'datasources:query',
},
{
path: 'api/v1/labels',
method: 'POST',
reqRole: 'Viewer',
reqAction: 'datasources:query',
},
{
path: 'api/v1/query_exemplars',
method: 'POST',
reqRole: 'Viewer',
reqAction: 'datasources:query',
},
{
path: '/rules',
method: 'GET',
reqRole: 'Viewer',
reqAction: 'alert.rules.external:read',
},
{
path: '/rules',
method: 'POST',
reqRole: 'Editor',
reqAction: 'alert.rules.external:write',
},
{
path: '/rules',
method: 'DELETE',
reqRole: 'Editor',
reqAction: 'alert.rules.external:write',
},
{
path: '/config/v1/rules',
method: 'DELETE',
reqRole: 'Editor',
reqAction: 'alert.rules.external:write',
},
{
path: '/config/v1/rules',
method: 'POST',
reqRole: 'Editor',
reqAction: 'alert.rules.external:write',
},
],
},
class: 'core',
module: {
path: 'core:plugin/prometheus',
loadingStrategy: 'script',
},
baseURL: 'app/plugins/datasource/prometheus',
signature: {
status: 'internal',
},
angular: {
detected: false,
},
},
status: {},
},
{
kind: 'Meta',
apiVersion: 'plugins.grafana.app/v0alpha1',
+1 -1
View File
@@ -52,7 +52,7 @@ func TestIntegrationCallResource(t *testing.T) {
cfg.StaticRootPath = staticRootPath
cfg.Azure = &azsettings.AzureSettings{}
coreRegistry := coreplugin.ProvideCoreRegistry(tracing.InitializeTracerForTest(), nil, &cloudwatch.Service{}, nil, nil, nil,
coreRegistry := coreplugin.ProvideCoreRegistry(tracing.InitializeTracerForTest(), nil, &cloudwatch.Service{}, nil, nil,
testdatasource.ProvideService(), nil, nil)
testCtx := pluginsintegration.CreateIntegrationTestCtx(t, cfg, coreRegistry)
-1
View File
@@ -549,7 +549,6 @@ import (
_ "github.com/grafana/grafana/pkg/tsdb/graphite"
_ "github.com/grafana/grafana/pkg/tsdb/influxdb"
_ "github.com/grafana/grafana/pkg/tsdb/mysql"
_ "github.com/grafana/grafana/pkg/tsdb/prometheus"
_ "github.com/grafana/grafana/pkg/util"
_ "github.com/grafana/grafana/pkg/util/errhttp"
_ "github.com/grafana/grafana/pkg/util/retryer"
-2
View File
@@ -192,7 +192,6 @@ import (
"github.com/grafana/grafana/pkg/tsdb/graphite"
"github.com/grafana/grafana/pkg/tsdb/influxdb"
"github.com/grafana/grafana/pkg/tsdb/mysql"
"github.com/grafana/grafana/pkg/tsdb/prometheus"
)
var withOTelSet = wire.NewSet(
@@ -305,7 +304,6 @@ var Basic = wire.NewSet(
influxdb.ProvideService,
wire.Bind(new(social.Service), new(*socialimpl.SocialService)),
graphite.ProvideService,
prometheus.ProvideService,
datasourceservice.ProvideCacheService,
wire.Bind(new(datasources.CacheService), new(*datasourceservice.CacheServiceImpl)),
encryptionservice.ProvideEncryptionService,
+2 -5
View File
@@ -261,7 +261,6 @@ import (
"github.com/grafana/grafana/pkg/tsdb/graphite"
"github.com/grafana/grafana/pkg/tsdb/influxdb"
"github.com/grafana/grafana/pkg/tsdb/mysql"
"github.com/grafana/grafana/pkg/tsdb/prometheus"
"github.com/stretchr/testify/mock"
)
@@ -356,7 +355,6 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts server.Options, apiO
cloudwatchService := cloudwatch.ProvideService()
graphiteService := graphite.ProvideService(httpclientProvider, tracer)
influxdbService := influxdb.ProvideService(httpclientProvider)
prometheusService := prometheus.ProvideService(httpclientProvider)
testdatasourceService := testdatasource.ProvideService()
mysqlService := mysql.ProvideService()
systemUsers := store.ProvideSystemUsersService()
@@ -365,7 +363,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts server.Options, apiO
return nil, err
}
grafanadsService := grafanads.ProvideService(storageService, featureToggles)
corepluginRegistry := coreplugin.ProvideCoreRegistry(tracer, azuremonitorService, cloudwatchService, graphiteService, influxdbService, prometheusService, testdatasourceService, mysqlService, grafanadsService)
corepluginRegistry := coreplugin.ProvideCoreRegistry(tracer, azuremonitorService, cloudwatchService, graphiteService, influxdbService, testdatasourceService, mysqlService, grafanadsService)
backendFactoryProvider := coreplugin.ProvideCoreProvider(corepluginRegistry)
processService := process.ProvideService()
legacyDatabaseProvider := legacysql.NewDatabaseProvider(sqlStore)
@@ -1118,7 +1116,6 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac
cloudwatchService := cloudwatch.ProvideService()
graphiteService := graphite.ProvideService(httpclientProvider, tracer)
influxdbService := influxdb.ProvideService(httpclientProvider)
prometheusService := prometheus.ProvideService(httpclientProvider)
testdatasourceService := testdatasource.ProvideService()
mysqlService := mysql.ProvideService()
systemUsers := store.ProvideSystemUsersService()
@@ -1127,7 +1124,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac
return nil, err
}
grafanadsService := grafanads.ProvideService(storageService, featureToggles)
corepluginRegistry := coreplugin.ProvideCoreRegistry(tracer, azuremonitorService, cloudwatchService, graphiteService, influxdbService, prometheusService, testdatasourceService, mysqlService, grafanadsService)
corepluginRegistry := coreplugin.ProvideCoreRegistry(tracer, azuremonitorService, cloudwatchService, graphiteService, influxdbService, testdatasourceService, mysqlService, grafanadsService)
backendFactoryProvider := coreplugin.ProvideCoreProvider(corepluginRegistry)
processService := process.ProvideService()
legacyDatabaseProvider := legacysql.NewDatabaseProvider(sqlStore)
-2
View File
@@ -191,7 +191,6 @@ import (
"github.com/grafana/grafana/pkg/tsdb/graphite"
"github.com/grafana/grafana/pkg/tsdb/influxdb"
"github.com/grafana/grafana/pkg/tsdb/mysql"
"github.com/grafana/grafana/pkg/tsdb/prometheus"
)
var withOTelSet = wire.NewSet(
@@ -304,7 +303,6 @@ var wireBasicSet = wire.NewSet(
influxdb.ProvideService,
wire.Bind(new(social.Service), new(*socialimpl.SocialService)),
graphite.ProvideService,
prometheus.ProvideService,
datasourceservice.ProvideCacheService,
wire.Bind(new(datasources.CacheService), new(*datasourceservice.CacheServiceImpl)),
encryptionservice.ProvideEncryptionService,
@@ -24,7 +24,6 @@ import (
"github.com/grafana/grafana/pkg/tsdb/graphite"
"github.com/grafana/grafana/pkg/tsdb/influxdb"
"github.com/grafana/grafana/pkg/tsdb/mysql"
"github.com/grafana/grafana/pkg/tsdb/prometheus"
)
const (
@@ -32,7 +31,6 @@ const (
AzureMonitor = "grafana-azure-monitor-datasource"
Graphite = "graphite"
InfluxDB = "influxdb"
Prometheus = "prometheus"
TestData = "grafana-testdata-datasource"
TestDataAlias = "testdata"
MySQL = "mysql"
@@ -77,8 +75,7 @@ func ProvideCoreProvider(coreRegistry *Registry) plugins.BackendFactoryProvider
}
func ProvideCoreRegistry(tracer trace.Tracer, am *azuremonitor.Service, cw *cloudwatch.Service,
grap *graphite.Service, idb *influxdb.Service,
pr *prometheus.Service, td *testdatasource.Service, my *mysql.Service,
grap *graphite.Service, idb *influxdb.Service, td *testdatasource.Service, my *mysql.Service,
graf *grafanads.Service) *Registry {
// Non-optimal global solution to replace plugin SDK default tracer for core plugins.
sdktracing.InitDefaultTracer(tracer)
@@ -88,7 +85,6 @@ func ProvideCoreRegistry(tracer trace.Tracer, am *azuremonitor.Service, cw *clou
AzureMonitor: asBackendPlugin(am),
Graphite: asBackendPlugin(grap),
InfluxDB: asBackendPlugin(idb),
Prometheus: asBackendPlugin(pr),
TestData: asBackendPlugin(td),
MySQL: asBackendPlugin(my),
Grafana: asBackendPlugin(graf),
@@ -199,8 +195,6 @@ func NewPlugin(pluginID string, httpClientProvider *httpclient.Provider, tracer
svc = graphite.ProvideService(httpClientProvider, tracer)
case InfluxDB:
svc = influxdb.ProvideService(httpClientProvider)
case Prometheus:
svc = prometheus.ProvideService(httpClientProvider)
case MySQL:
svc = mysql.ProvideService()
default:
@@ -23,7 +23,6 @@ func TestNewPlugin(t *testing.T) {
{ID: Graphite},
{ID: InfluxDB},
{ID: MySQL},
{ID: Prometheus},
{ID: TestData, ExpectedAlias: TestDataAlias},
{ID: TestDataAlias, ExpectedID: TestData, ExpectedAlias: TestDataAlias},
}
@@ -33,7 +33,6 @@ import (
"github.com/grafana/grafana/pkg/tsdb/graphite"
"github.com/grafana/grafana/pkg/tsdb/influxdb"
"github.com/grafana/grafana/pkg/tsdb/mysql"
"github.com/grafana/grafana/pkg/tsdb/prometheus"
"github.com/grafana/grafana/pkg/util/testutil"
)
@@ -140,11 +139,10 @@ func TestIntegrationPluginManager(t *testing.T) {
cw := cloudwatch.ProvideService()
grap := graphite.ProvideService(hcp, tracer)
idb := influxdb.ProvideService(hcp)
pr := prometheus.ProvideService(hcp)
td := testdatasource.ProvideService()
my := mysql.ProvideService()
graf := grafanads.ProvideService(nil, features)
coreRegistry := coreplugin.ProvideCoreRegistry(tracing.InitializeTracerForTest(), am, cw, grap, idb, pr, td, my, graf)
coreRegistry := coreplugin.ProvideCoreRegistry(tracing.InitializeTracerForTest(), am, cw, grap, idb, td, my, graf)
testCtx := pluginsintegration.CreateIntegrationTestCtx(t, cfg, coreRegistry)
@@ -221,7 +219,6 @@ func verifyCorePluginCatalogue(t *testing.T, ctx context.Context, ps *pluginstor
"grafana-azure-monitor-datasource": {},
"graphite": {},
"influxdb": {},
"prometheus": {},
"grafana-testdata-datasource": {},
"mysql": {},
"grafana": {},
@@ -78,7 +78,7 @@ func TestIntegrationDataSourceGetByUID(t *testing.T) {
func runGetTests(t *testing.T, ctx context.Context, grafanaListeningAddr string, testEnv *server.TestEnv, store db.DB, cfg *setting.Cfg, modePrefix string) {
t.Run("GET - succeeds", func(t *testing.T) {
// make a unique-enough UID - all the tests in this file share the same grafana instance.
uid := fmt.Sprintf("%s-prometheus-get", modePrefix)
uid := fmt.Sprintf("%s-testdata-get", modePrefix)
jsonData := simplejson.NewFromAny(map[string]any{
"httpMethod": "POST",
})
@@ -88,8 +88,8 @@ func runGetTests(t *testing.T, ctx context.Context, grafanaListeningAddr string,
&datasources.AddDataSourceCommand{
OrgID: 1,
Access: datasources.DS_ACCESS_PROXY,
Name: "Test Prometheus",
Type: datasources.DS_PROMETHEUS,
Name: "Test Data",
Type: datasources.DS_TESTDATA,
UID: uid,
URL: "http://localhost:9090",
User: "testuser",
@@ -115,8 +115,8 @@ func runGetTests(t *testing.T, ctx context.Context, grafanaListeningAddr string,
require.NoError(t, resp.Body.Close())
assert.Equal(t, uid, dto.UID)
assert.Equal(t, "Test Prometheus", dto.Name)
assert.Equal(t, "prometheus", dto.Type)
assert.Equal(t, "Test Data", dto.Name)
assert.Equal(t, datasources.DS_TESTDATA, dto.Type)
assert.Equal(t, datasources.DsAccess(datasources.DS_ACCESS_PROXY), dto.Access)
assert.Equal(t, "http://localhost:9090", dto.Url)
assert.Equal(t, "testuser", dto.User)
@@ -165,7 +165,7 @@ func runGetTests(t *testing.T, ctx context.Context, grafanaListeningAddr string,
OrgID: 1,
Access: datasources.DS_ACCESS_PROXY,
Name: "Test DS for GET Permissions 1",
Type: datasources.DS_PROMETHEUS,
Type: datasources.DS_TESTDATA,
UID: dsUID,
URL: "http://localhost:9090",
})
@@ -197,7 +197,7 @@ func runGetTests(t *testing.T, ctx context.Context, grafanaListeningAddr string,
OrgID: 1,
Access: datasources.DS_ACCESS_PROXY,
Name: "Test DS for GET Permissions 2",
Type: datasources.DS_PROMETHEUS,
Type: datasources.DS_TESTDATA,
UID: dsUID,
URL: "http://localhost:9090",
})
@@ -229,7 +229,7 @@ func runGetTests(t *testing.T, ctx context.Context, grafanaListeningAddr string,
OrgID: 1,
Access: datasources.DS_ACCESS_PROXY,
Name: "Test DS for GET Permissions 3",
Type: datasources.DS_PROMETHEUS,
Type: datasources.DS_TESTDATA,
UID: dsUID,
URL: "http://localhost:9090",
})
@@ -1003,7 +1003,7 @@ func createTestDataSource(t *testing.T, ctx context.Context, dsService datasourc
OrgID: 1,
Access: datasources.DS_ACCESS_PROXY,
Name: name,
Type: datasources.DS_PROMETHEUS,
Type: datasources.DS_TESTDATA,
UID: uid,
URL: "http://localhost:9090",
})
@@ -1253,60 +1253,6 @@
"signatureOrg": "",
"angularDetected": false
},
{
"name": "Prometheus",
"type": "datasource",
"id": "prometheus",
"enabled": true,
"pinned": false,
"info": {
"author": {
"name": "Grafana Labs",
"url": "https://grafana.com"
},
"description": "Open source time series database \u0026 alerting",
"links": [
{
"name": "Learn more",
"url": "https://prometheus.io/"
},
{
"name": "Raise issue",
"url": "https://github.com/grafana/grafana/issues/new"
},
{
"name": "Documentation",
"url": "https://grafana.com/docs/grafana/latest/datasources/prometheus/"
}
],
"logos": {
"small": "public/plugins/prometheus/img/prometheus_logo.svg",
"large": "public/plugins/prometheus/img/prometheus_logo.svg"
},
"build": {},
"screenshots": null,
"version": "",
"updated": "",
"keywords": null
},
"dependencies": {
"grafanaDependency": "",
"grafanaVersion": "*",
"plugins": [],
"extensions": {
"exposedComponents": []
}
},
"latestVersion": "",
"hasUpdate": false,
"defaultNavUrl": "/plugins/prometheus/",
"category": "tsdb",
"state": "",
"signature": "internal",
"signatureType": "",
"signatureOrg": "",
"angularDetected": false
},
{
"name": "Stat",
"type": "panel",
-132
View File
@@ -1,132 +0,0 @@
package prometheus
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/tests/testinfra"
"github.com/grafana/grafana/pkg/tests/testsuite"
"github.com/grafana/grafana/pkg/util/testutil"
)
func TestMain(m *testing.M) {
testsuite.Run(m)
}
func TestIntegrationPrometheus(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
})
grafanaListeningAddr, testEnv := testinfra.StartGrafanaEnv(t, dir, path)
ctx := context.Background()
var outgoingRequest *http.Request
outgoingServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
outgoingRequest = r
w.WriteHeader(http.StatusUnauthorized)
}))
t.Cleanup(outgoingServer.Close)
jsonData := simplejson.NewFromAny(map[string]any{
"httpMethod": "post",
"httpHeaderName1": "X-CUSTOM-HEADER",
"customQueryParameters": "q1=1&q2=2",
})
secureJSONData := map[string]string{
"basicAuthPassword": "basicAuthPassword",
"httpHeaderValue1": "custom-header-value",
}
uid := "prometheus"
_, err := testEnv.Server.HTTPServer.DataSourcesService.AddDataSource(ctx, &datasources.AddDataSourceCommand{
OrgID: 1,
Access: datasources.DS_ACCESS_PROXY,
Name: "Prometheus",
Type: datasources.DS_PROMETHEUS,
UID: uid,
URL: outgoingServer.URL,
BasicAuth: true,
BasicAuthUser: "basicAuthUser",
JsonData: jsonData,
SecureJsonData: secureJSONData,
})
require.NoError(t, err)
t.Run("When calling /api/ds/query should set expected headers on outgoing HTTP request", func(t *testing.T) {
query := simplejson.NewFromAny(map[string]any{
"datasource": map[string]any{
"uid": uid,
},
"expr": "1",
"instantQuery": true,
})
buf1 := &bytes.Buffer{}
err = json.NewEncoder(buf1).Encode(dtos.MetricRequest{
From: "1668078080000",
To: "1668081680000",
Queries: []*simplejson.Json{query},
})
require.NoError(t, err)
u := fmt.Sprintf("http://admin:admin@%s/api/ds/query", grafanaListeningAddr)
// nolint:gosec
resp, err := http.Post(u, "application/json", buf1)
require.NoError(t, err)
t.Cleanup(func() {
_ = resp.Body.Close()
})
require.NotNil(t, outgoingRequest)
require.Equal(t, "/api/v1/query_range?q1=1&q2=2", outgoingRequest.URL.String())
require.Equal(t, "custom-header-value", outgoingRequest.Header.Get("X-CUSTOM-HEADER"))
username, pwd, ok := outgoingRequest.BasicAuth()
require.True(t, ok)
require.Equal(t, "basicAuthUser", username)
require.Equal(t, "basicAuthPassword", pwd)
})
t.Run("When calling /api/ds/query should set expected headers on outgoing HTTP request", func(t *testing.T) {
query := simplejson.NewFromAny(map[string]any{
"datasource": map[string]any{
"uid": uid,
},
"expr": "up",
"instantQuery": true,
})
buf1 := &bytes.Buffer{}
err = json.NewEncoder(buf1).Encode(dtos.MetricRequest{
From: "now-1h",
To: "now",
Queries: []*simplejson.Json{query},
})
require.NoError(t, err)
u := fmt.Sprintf("http://admin:admin@%s/api/ds/query", grafanaListeningAddr)
// nolint:gosec
resp, err := http.Post(u, "application/json", buf1)
require.NoError(t, err)
t.Cleanup(func() {
_ = resp.Body.Close()
})
require.NotNil(t, outgoingRequest)
require.Equal(t, "/api/v1/query_range", outgoingRequest.URL.Path)
require.Contains(t, outgoingRequest.URL.String(), "?q1=1&q2=2")
require.Equal(t, "custom-header-value", outgoingRequest.Header.Get("X-CUSTOM-HEADER"))
username, pwd, ok := outgoingRequest.BasicAuth()
require.True(t, ok)
require.Equal(t, "basicAuthUser", username)
require.Equal(t, "basicAuthPassword", pwd)
})
}
@@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user"
@@ -37,9 +38,8 @@ func TestPublicDashboardQueryAPI(t *testing.T) {
datasourcePayload := map[string]interface{}{
"name": "Test Data Source",
"type": "prometheus",
"uid": "prometheus",
"url": "http://localhost:9090",
"type": datasources.DS_TESTDATA,
"uid": "testdata",
"access": "proxy",
}
datasourceBytes, err := json.Marshal(datasourcePayload)
@@ -64,11 +64,11 @@ func TestPublicDashboardQueryAPI(t *testing.T) {
"title": "Test Panel",
"targets": []map[string]interface{}{
{
"refId": "A",
"expr": "up",
"refId": "A",
"scenarioId": "random_walk",
"datasource": map[string]interface{}{
"type": "prometheus",
"uid": "prometheus",
"type": datasources.DS_TESTDATA,
"uid": "testdata",
},
},
},
+34 -1
View File
@@ -4,6 +4,8 @@ import (
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"sync"
"testing"
@@ -87,12 +89,43 @@ func TestIntegrationDatasourceProxy(t *testing.T) {
return last
}
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
grafanaOpts := testinfra.GrafanaOpts{
DisableAnonymous: true,
EnableFeatureToggles: []string{
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, // start the datasource api servers
featuremgmt.FlagDatasourceUseNewCRUDAPIs, // register the datasource api groups
},
}
grafanaDir, cfgPath := testinfra.CreateGrafDir(t, grafanaOpts)
pluginDir := filepath.Join(grafanaDir, "plugins", datasources.DS_PROMETHEUS)
require.NoError(t, os.MkdirAll(pluginDir, 0o750))
require.NoError(t, os.WriteFile(filepath.Join(pluginDir, "plugin.json"), []byte(`{
"type": "datasource",
"name": "Prometheus",
"id": "prometheus",
"routes": [
{
"method": "GET",
"path": "/rules",
"reqRole": "Viewer",
"reqAction": "alert.rules.external:read"
},
{
"method": "DELETE",
"path": "/rules",
"reqRole": "Editor",
"reqAction": "alert.rules.external:write"
}
],
"info": {
"version": "1.0.0"
}
}`), 0o600))
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
Dir: grafanaDir,
DirPath: cfgPath,
})
ds := helper.CreateDS(&datasources.AddDataSourceCommand{
-65
View File
@@ -1,65 +0,0 @@
package prometheus
import (
"context"
"github.com/grafana/grafana-plugin-sdk-go/backend"
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana-prometheus-datasource/pkg/promlib"
)
type prometheusLib interface {
QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error)
CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error
GetBuildInfo(ctx context.Context, req promlib.BuildInfoRequest) (*promlib.BuildInfoResponse, error)
GetHeuristics(ctx context.Context, req promlib.HeuristicsRequest) (*promlib.Heuristics, error)
CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error)
ValidateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error)
MutateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error)
ConvertObjects(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error)
}
type Service struct {
lib prometheusLib
}
func ProvideService(httpClientProvider *sdkhttpclient.Provider) *Service {
plog := backend.NewLoggerWith("logger", "tsdb.prometheus")
plog.Debug("Initializing")
return &Service{
lib: promlib.NewService(httpClientProvider, plog, nil),
}
}
func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
return s.lib.QueryData(ctx, req)
}
func (s *Service) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
return s.lib.CallResource(ctx, req, sender)
}
func (s *Service) GetBuildInfo(ctx context.Context, req promlib.BuildInfoRequest) (*promlib.BuildInfoResponse, error) {
return s.lib.GetBuildInfo(ctx, req)
}
func (s *Service) GetHeuristics(ctx context.Context, req promlib.HeuristicsRequest) (*promlib.Heuristics, error) {
return s.lib.GetHeuristics(ctx, req)
}
func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult,
error) {
return s.lib.CheckHealth(ctx, req)
}
func (s *Service) ValidateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
return s.lib.ValidateAdmission(ctx, req)
}
func (s *Service) MutateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
return s.lib.MutateAdmission(ctx, req)
}
func (s *Service) ConvertObjects(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
return s.lib.ConvertObjects(ctx, req)
}
-185
View File
@@ -1,185 +0,0 @@
package prometheus
import (
"context"
"errors"
"testing"
"github.com/grafana/grafana-plugin-sdk-go/backend"
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana-prometheus-datasource/pkg/promlib"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type mockLib struct {
queryDataFn func(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error)
callResourceFn func(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error
getBuildInfoFn func(ctx context.Context, req promlib.BuildInfoRequest) (*promlib.BuildInfoResponse, error)
getHeuristicsFn func(ctx context.Context, req promlib.HeuristicsRequest) (*promlib.Heuristics, error)
checkHealthFn func(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error)
validateAdmissionFn func(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error)
mutateAdmissionFn func(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error)
convertObjectsFn func(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error)
}
func (m *mockLib) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
return m.queryDataFn(ctx, req)
}
func (m *mockLib) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
return m.callResourceFn(ctx, req, sender)
}
func (m *mockLib) GetBuildInfo(ctx context.Context, req promlib.BuildInfoRequest) (*promlib.BuildInfoResponse, error) {
return m.getBuildInfoFn(ctx, req)
}
func (m *mockLib) GetHeuristics(ctx context.Context, req promlib.HeuristicsRequest) (*promlib.Heuristics, error) {
return m.getHeuristicsFn(ctx, req)
}
func (m *mockLib) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
return m.checkHealthFn(ctx, req)
}
func (m *mockLib) ValidateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
return m.validateAdmissionFn(ctx, req)
}
func (m *mockLib) MutateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
return m.mutateAdmissionFn(ctx, req)
}
func (m *mockLib) ConvertObjects(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
return m.convertObjectsFn(ctx, req)
}
func TestProvideService(t *testing.T) {
svc := ProvideService(sdkhttpclient.NewProvider())
require.NotNil(t, svc)
require.NotNil(t, svc.lib)
}
func TestQueryData(t *testing.T) {
want := &backend.QueryDataResponse{}
svc := &Service{lib: &mockLib{
queryDataFn: func(_ context.Context, _ *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
return want, nil
},
}}
got, err := svc.QueryData(context.Background(), &backend.QueryDataRequest{})
require.NoError(t, err)
assert.Same(t, want, got)
}
func TestQueryData_Error(t *testing.T) {
wantErr := errors.New("query failed")
svc := &Service{lib: &mockLib{
queryDataFn: func(_ context.Context, _ *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
return nil, wantErr
},
}}
_, err := svc.QueryData(context.Background(), &backend.QueryDataRequest{})
assert.ErrorIs(t, err, wantErr)
}
func TestCallResource(t *testing.T) {
called := false
svc := &Service{lib: &mockLib{
callResourceFn: func(_ context.Context, _ *backend.CallResourceRequest, _ backend.CallResourceResponseSender) error {
called = true
return nil
},
}}
err := svc.CallResource(context.Background(), &backend.CallResourceRequest{}, nil)
require.NoError(t, err)
assert.True(t, called)
}
func TestCallResource_Error(t *testing.T) {
wantErr := errors.New("resource failed")
svc := &Service{lib: &mockLib{
callResourceFn: func(_ context.Context, _ *backend.CallResourceRequest, _ backend.CallResourceResponseSender) error {
return wantErr
},
}}
err := svc.CallResource(context.Background(), &backend.CallResourceRequest{}, nil)
assert.ErrorIs(t, err, wantErr)
}
func TestGetBuildInfo(t *testing.T) {
want := &promlib.BuildInfoResponse{}
svc := &Service{lib: &mockLib{
getBuildInfoFn: func(_ context.Context, _ promlib.BuildInfoRequest) (*promlib.BuildInfoResponse, error) {
return want, nil
},
}}
got, err := svc.GetBuildInfo(context.Background(), promlib.BuildInfoRequest{})
require.NoError(t, err)
assert.Same(t, want, got)
}
func TestGetHeuristics(t *testing.T) {
want := &promlib.Heuristics{}
svc := &Service{lib: &mockLib{
getHeuristicsFn: func(_ context.Context, _ promlib.HeuristicsRequest) (*promlib.Heuristics, error) {
return want, nil
},
}}
got, err := svc.GetHeuristics(context.Background(), promlib.HeuristicsRequest{})
require.NoError(t, err)
assert.Same(t, want, got)
}
func TestCheckHealth(t *testing.T) {
want := &backend.CheckHealthResult{Status: backend.HealthStatusOk}
svc := &Service{lib: &mockLib{
checkHealthFn: func(_ context.Context, _ *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
return want, nil
},
}}
got, err := svc.CheckHealth(context.Background(), &backend.CheckHealthRequest{})
require.NoError(t, err)
assert.Same(t, want, got)
}
func TestCheckHealth_Error(t *testing.T) {
wantErr := errors.New("health check failed")
svc := &Service{lib: &mockLib{
checkHealthFn: func(_ context.Context, _ *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
return nil, wantErr
},
}}
_, err := svc.CheckHealth(context.Background(), &backend.CheckHealthRequest{})
assert.ErrorIs(t, err, wantErr)
}
func TestValidateAdmission(t *testing.T) {
want := &backend.ValidationResponse{Allowed: true}
svc := &Service{lib: &mockLib{
validateAdmissionFn: func(_ context.Context, _ *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
return want, nil
},
}}
got, err := svc.ValidateAdmission(context.Background(), &backend.AdmissionRequest{})
require.NoError(t, err)
assert.Same(t, want, got)
}
func TestMutateAdmission(t *testing.T) {
want := &backend.MutationResponse{Allowed: true}
svc := &Service{lib: &mockLib{
mutateAdmissionFn: func(_ context.Context, _ *backend.AdmissionRequest) (*backend.MutationResponse, error) {
return want, nil
},
}}
got, err := svc.MutateAdmission(context.Background(), &backend.AdmissionRequest{})
require.NoError(t, err)
assert.Same(t, want, got)
}
func TestConvertObjects(t *testing.T) {
want := &backend.ConversionResponse{}
svc := &Service{lib: &mockLib{
convertObjectsFn: func(_ context.Context, _ *backend.ConversionRequest) (*backend.ConversionResponse, error) {
return want, nil
},
}}
got, err := svc.ConvertObjects(context.Background(), &backend.ConversionRequest{})
require.NoError(t, err)
assert.Same(t, want, got)
}
@@ -1,6 +1,7 @@
import { render } from 'test/test-utils';
import { byTestId, byText } from 'testing-library-selector';
import { type DataSourceApi } from '@grafana/data';
import { type PromOptions } from '@grafana/prometheus';
import { setPluginLinksHook } from '@grafana/runtime';
import config from 'app/core/config';
@@ -197,7 +198,24 @@ describe('PanelAlertTabContent', () => {
AccessControlAction.AlertingRuleExternalRead,
AccessControlAction.AlertingRuleExternalWrite,
]);
setupDataSources(...Object.values(dataSources));
const dataSourceSrv = setupDataSources(...Object.values(dataSources));
jest.spyOn(dataSourceSrv, 'get').mockImplementation(async (ref) => {
const settings = dataSourceSrv.getInstanceSettings(ref);
const options = settings?.jsonData as PromOptions | undefined;
const dataSource: Partial<DataSourceApi> = {
uid: settings?.uid,
type: settings?.type,
interval: options?.timeInterval ?? '15s',
interpolateVariablesInQueries: (queries, scopedVars) =>
queries.map((query) => ({
...query,
datasource: { uid: settings?.uid, type: settings?.type },
expr: (query as { expr?: string }).expr?.replace('$__interval', String(scopedVars.__interval?.value)),
interval: '',
})),
};
return dataSource as DataSourceApi;
});
setPluginLinksHook(() => ({
links: [],
@@ -1,10 +1,11 @@
import { Icon } from '@grafana/ui';
import mimirLogoSvg from 'app/plugins/datasource/prometheus/img/mimir_logo.svg';
import prometheusLogoSvg from 'app/plugins/datasource/prometheus/img/prometheus_logo.svg';
import { PromApplication, type RulesSourceApplication } from 'app/types/unified-alerting-dto';
import lokiIconSvg from '../../../../loki-helpers/loki_icon.svg';
import mimirLogoSvg from './img/mimir_logo.svg';
import prometheusLogoSvg from './img/prometheus_logo.svg';
interface DataSourceIconProps {
application?: RulesSourceApplication;
size?: number;
@@ -2,6 +2,7 @@ import userEvent from '@testing-library/user-event';
import { render } from 'test/test-utils';
import { byTestId } from 'testing-library-selector';
import { type DataSourceApi } from '@grafana/data';
import { type PromOptions } from '@grafana/prometheus';
import { config, locationService, setPluginLinksHook } from '@grafana/runtime';
import * as ruler from 'app/features/alerting/unified/api/ruler';
@@ -189,7 +190,24 @@ describe('PanelAlertTabContent', () => {
AccessControlAction.AlertingRuleExternalWrite,
]);
setupDataSources(...Object.values(dataSources));
const dataSourceSrv = setupDataSources(...Object.values(dataSources));
jest.spyOn(dataSourceSrv, 'get').mockImplementation(async (ref) => {
const settings = dataSourceSrv.getInstanceSettings(ref);
const options = settings?.jsonData as PromOptions | undefined;
const dataSource: Partial<DataSourceApi> = {
uid: settings?.uid,
type: settings?.type,
interval: options?.timeInterval ?? '15s',
interpolateVariablesInQueries: (queries, scopedVars) =>
queries.map((query) => ({
...query,
datasource: { uid: settings?.uid, type: settings?.type },
expr: (query as { expr?: string }).expr?.replace('$__interval', String(scopedVars.__interval?.value)),
interval: '',
})),
};
return dataSource as DataSourceApi;
});
mocks.rulerBuilderMock.mockReturnValue({
rules: () => ({ path: `api/ruler/${GRAFANA_RULES_SOURCE_NAME}/api/v1/rules` }),
@@ -10,8 +10,6 @@ const influxdbPlugin = async () =>
await import(/* webpackChunkName: "influxdbPlugin" */ 'app/plugins/datasource/influxdb/module');
const mixedPlugin = async () =>
await import(/* webpackChunkName: "mixedPlugin" */ 'app/plugins/datasource/mixed/module');
const prometheusPlugin = async () =>
await import(/* webpackChunkName: "prometheusPlugin" */ 'app/plugins/datasource/prometheus/module');
const alertmanagerPlugin = async () =>
await import(/* webpackChunkName: "alertmanagerPlugin" */ 'app/plugins/datasource/alertmanager/module');
@@ -74,7 +72,6 @@ const builtInPlugins: Record<string, System.Module | (() => Promise<System.Modul
'core:plugin/grafana': grafanaPlugin,
'core:plugin/influxdb': influxdbPlugin,
'core:plugin/mixed': mixedPlugin,
'core:plugin/prometheus': prometheusPlugin,
'core:plugin/alertmanager': alertmanagerPlugin,
// panels
'core:plugin/text': () =>
@@ -1,7 +0,0 @@
# Prometheus Data Source - Native Plugin
Grafana ships with **built in** support for Prometheus, the open-source service monitoring system and time series database.
Read more about it here:
[http://docs.grafana.org/datasources/prometheus/](http://docs.grafana.org/datasources/prometheus/)
@@ -1,52 +0,0 @@
import { render, screen } from '@testing-library/react';
import { type DataSourceSettings } from '@grafana/data';
import { type PromOptions } from '@grafana/prometheus';
import { ConfigEditor } from './ConfigEditor';
function createDefaultConfigOptions(): DataSourceSettings<PromOptions> {
return {
jsonData: {},
secureJsonFields: {},
access: 'proxy',
} as DataSourceSettings<PromOptions>;
}
describe('ConfigEditor', () => {
it('renders without error', () => {
expect(() =>
render(<ConfigEditor onOptionsChange={() => {}} options={createDefaultConfigOptions()} />)
).not.toThrow();
});
it('shows browser access mode alert when access is direct', () => {
const options = { ...createDefaultConfigOptions(), access: 'direct' as const };
render(<ConfigEditor onOptionsChange={() => {}} options={options} />);
expect(
screen.getByText(/Browser access mode in the Prometheus data source is no longer available/)
).toBeInTheDocument();
});
it('does not show browser access mode alert for proxy access', () => {
render(<ConfigEditor onOptionsChange={() => {}} options={createDefaultConfigOptions()} />);
expect(
screen.queryByText(/Browser access mode in the Prometheus data source is no longer available/)
).not.toBeInTheDocument();
});
it('renders the Prometheus server URL input', () => {
render(<ConfigEditor onOptionsChange={() => {}} options={createDefaultConfigOptions()} />);
expect(screen.getByPlaceholderText('http://localhost:9090')).toBeInTheDocument();
});
it('renders the Authentication section', () => {
render(<ConfigEditor onOptionsChange={() => {}} options={createDefaultConfigOptions()} />);
expect(screen.getByRole('heading', { name: 'Authentication' })).toBeInTheDocument();
});
it('renders the Advanced settings section', () => {
render(<ConfigEditor onOptionsChange={() => {}} options={createDefaultConfigOptions()} />);
expect(screen.getByText('Advanced settings')).toBeInTheDocument();
});
});
@@ -1,105 +0,0 @@
import { css } from '@emotion/css';
import { type DataSourcePluginOptionsEditorProps, type GrafanaTheme2 } from '@grafana/data';
import { AdvancedHttpSettings, ConfigSection, DataSourceDescription } from '@grafana/plugin-ui';
import { AlertingSettingsOverhaul, type PromOptions, PromSettings } from '@grafana/prometheus';
import { config } from '@grafana/runtime';
import { Alert, useTheme2 } from '@grafana/ui';
import { HttpSettings } from './HttpSettings';
export type Props = DataSourcePluginOptionsEditorProps<PromOptions>;
export const ConfigEditor = (props: Props) => {
const { options, onOptionsChange } = props;
const theme = useTheme2();
const styles = overhaulStyles(theme);
return (
<>
{options.access === 'direct' && (
<Alert title="Error" severity="error">
Browser access mode in the Prometheus data source is no longer available. Switch to server access mode.
</Alert>
)}
<DataSourceDescription
dataSourceName="Prometheus"
docsLink="https://grafana.com/docs/grafana/latest/datasources/prometheus/configure/"
/>
<hr className={`${styles.hrTopSpace} ${styles.hrBottomSpace}`} />
<HttpSettings
options={options}
onOptionsChange={onOptionsChange}
secureSocksDSProxyEnabled={config.secureSocksDSProxyEnabled}
/>
<hr />
<ConfigSection
className={styles.advancedSettings}
title="Advanced settings"
description="Additional settings are optional settings that can be configured for more control over your data source."
>
<AdvancedHttpSettings
className={styles.advancedHTTPSettingsMargin}
config={options}
onChange={onOptionsChange}
/>
<AlertingSettingsOverhaul<PromOptions> options={options} onOptionsChange={onOptionsChange} />
<PromSettings options={options} onOptionsChange={onOptionsChange} />
</ConfigSection>
</>
);
};
function overhaulStyles(theme: GrafanaTheme2) {
return {
additionalSettings: css({
marginBottom: '25px',
}),
secondaryGrey: css({
color: `${theme.colors.secondary.text}`,
opacity: '65%',
}),
inlineError: css({
margin: '0px 0px 4px 245px',
}),
switchField: css({
alignItems: 'center',
}),
sectionHeaderPadding: css({
paddingTop: '32px',
}),
sectionBottomPadding: css({
paddingBottom: '28px',
}),
subsectionText: css({
fontSize: '12px',
}),
hrBottomSpace: css({
marginBottom: '56px',
}),
hrTopSpace: css({
marginTop: '50px',
}),
textUnderline: css({
textDecoration: 'underline',
}),
versionMargin: css({
marginBottom: '12px',
}),
advancedHTTPSettingsMargin: css({
margin: '24px 0 8px 0',
}),
advancedSettings: css({
paddingTop: '32px',
}),
alertingTop: css({
marginTop: '40px !important',
}),
overhaulPageHeading: css({
fontWeight: '400',
}),
container: css({
maxwidth: '578',
}),
};
}
@@ -1,122 +0,0 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { type DataSourceSettings } from '@grafana/data';
import { type PromOptions } from '@grafana/prometheus';
import { HttpSettings } from './HttpSettings';
// Replace Auth with a test double that exposes onAuthMethodSelect as buttons,
// so we can test the callback logic without depending on the Auth component's internal UI.
jest.mock('@grafana/plugin-ui', () => ({
...jest.requireActual('@grafana/plugin-ui'),
Auth: ({ onAuthMethodSelect }: { onAuthMethodSelect: (method: string) => void }) => (
<div>
<button onClick={() => onAuthMethodSelect('BasicAuth')}>Basic auth</button>
<button onClick={() => onAuthMethodSelect('OAuthForward')}>Forward OAuth Identity</button>
<button onClick={() => onAuthMethodSelect('CrossSiteCredentials')}>Cross-site credentials</button>
</div>
),
}));
function createDefaultConfigOptions(): DataSourceSettings<PromOptions> {
return {
jsonData: {},
secureJsonFields: {},
access: 'proxy',
basicAuth: false,
withCredentials: false,
} as DataSourceSettings<PromOptions>;
}
describe('HttpSettings', () => {
it('renders without error', () => {
expect(() =>
render(
<HttpSettings
options={createDefaultConfigOptions()}
onOptionsChange={() => {}}
secureSocksDSProxyEnabled={false}
/>
)
).not.toThrow();
});
it('renders the Prometheus server URL input', () => {
render(
<HttpSettings
options={createDefaultConfigOptions()}
onOptionsChange={() => {}}
secureSocksDSProxyEnabled={false}
/>
);
expect(screen.getByPlaceholderText('http://localhost:9090')).toBeInTheDocument();
});
it('shows SecureSocksProxy settings when enabled', () => {
render(
<HttpSettings
options={createDefaultConfigOptions()}
onOptionsChange={() => {}}
secureSocksDSProxyEnabled={true}
/>
);
expect(screen.getByText('Secure Socks Proxy')).toBeInTheDocument();
});
it('hides SecureSocksProxy settings when disabled', () => {
render(
<HttpSettings
options={createDefaultConfigOptions()}
onOptionsChange={() => {}}
secureSocksDSProxyEnabled={false}
/>
);
expect(screen.queryByText('Secure Socks Proxy')).not.toBeInTheDocument();
});
it('calls onOptionsChange with basicAuth:true when BasicAuth is selected', async () => {
const onOptionsChange = jest.fn();
render(
<HttpSettings
options={createDefaultConfigOptions()}
onOptionsChange={onOptionsChange}
secureSocksDSProxyEnabled={false}
/>
);
await userEvent.click(screen.getByRole('button', { name: 'Basic auth' }));
expect(onOptionsChange).toHaveBeenCalledWith(expect.objectContaining({ basicAuth: true, withCredentials: false }));
});
it('calls onOptionsChange with oauthPassThru:true when OAuthForward is selected', async () => {
const onOptionsChange = jest.fn();
render(
<HttpSettings
options={createDefaultConfigOptions()}
onOptionsChange={onOptionsChange}
secureSocksDSProxyEnabled={false}
/>
);
await userEvent.click(screen.getByRole('button', { name: 'Forward OAuth Identity' }));
expect(onOptionsChange).toHaveBeenCalledWith(
expect.objectContaining({
basicAuth: false,
withCredentials: false,
jsonData: expect.objectContaining({ oauthPassThru: true }),
})
);
});
it('calls onOptionsChange with withCredentials:true when CrossSiteCredentials is selected', async () => {
const onOptionsChange = jest.fn();
render(
<HttpSettings
options={createDefaultConfigOptions()}
onOptionsChange={onOptionsChange}
secureSocksDSProxyEnabled={false}
/>
);
await userEvent.click(screen.getByRole('button', { name: 'Cross-site credentials' }));
expect(onOptionsChange).toHaveBeenCalledWith(expect.objectContaining({ basicAuth: false, withCredentials: true }));
});
});
@@ -1,83 +0,0 @@
import { type DataSourceSettings } from '@grafana/data';
import { Auth, AuthMethod, ConnectionSettings, convertLegacyAuthProps } from '@grafana/plugin-ui';
import { docsTip, overhaulStyles, type PromOptions } from '@grafana/prometheus';
import { SecureSocksProxySettings, useTheme2 } from '@grafana/ui';
type Props = {
options: DataSourceSettings<PromOptions>;
onOptionsChange: (options: DataSourceSettings<PromOptions>) => void;
secureSocksDSProxyEnabled: boolean;
};
export const HttpSettings = (props: Props) => {
const { options, onOptionsChange, secureSocksDSProxyEnabled } = props;
const newAuthProps = convertLegacyAuthProps({
config: options,
onChange: onOptionsChange,
});
const theme = useTheme2();
const styles = overhaulStyles(theme);
// Do we need this switch anymore? Update the language.
let urlTooltip;
switch (options.access) {
case 'direct':
urlTooltip = (
<>
Your access method is <em>Browser</em>, this means the URL needs to be accessible from the browser.
{docsTip()}
</>
);
break;
case 'proxy':
urlTooltip = (
<>
Your access method is <em>Server</em>, this means the URL needs to be accessible from the grafana
backend/server.
{docsTip()}
</>
);
break;
default:
urlTooltip = <>Specify a complete HTTP URL (for example http://your_server:8080) {docsTip()}</>;
}
return (
<>
<ConnectionSettings
urlPlaceholder="http://localhost:9090"
config={options}
onChange={onOptionsChange}
urlLabel="Prometheus server URL"
urlTooltip={urlTooltip}
/>
<hr className={`${styles.hrTopSpace} ${styles.hrBottomSpace}`} />
<Auth
{...newAuthProps}
onAuthMethodSelect={(method) => {
onOptionsChange({
...options,
basicAuth: method === AuthMethod.BasicAuth,
withCredentials: method === AuthMethod.CrossSiteCredentials,
jsonData: {
...options.jsonData,
oauthPassThru: method === AuthMethod.OAuthForward,
},
});
}}
// If your method is selected pass its id to `selectedMethod`,
// otherwise pass the id from converted legacy data
selectedMethod={newAuthProps.selectedMethod}
/>
<div className={styles.sectionBottomPadding} />
{secureSocksDSProxyEnabled && (
<>
<SecureSocksProxySettings options={options} onOptionsChange={onOptionsChange} />
<div className={styles.sectionBottomPadding} />
</>
)}
</>
);
};
@@ -1 +0,0 @@
<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg"><g fill="#3b697e"><path d="m64.001 32c0-17.646-14.356-32.001-32.001-32.001-17.646 0-32.001 14.355-32.001 32.001s14.355 32.001 32.001 32.001 32.001-14.355 32.001-32.001zm-62.592 0c0-16.868 13.723-30.591 30.591-30.591s30.591 13.723 30.591 30.591-13.723 30.591-30.591 30.591-30.591-13.723-30.591-30.591z" fill-rule="nonzero"/><circle cx="49.3" cy="32.725" r="5.296"/><circle cx="31.875" cy="52.94" r="5.296"/><path d="m31.148 33.103 3.295-8.185 2.988 8.538h2.684c.226 2.71 1.641 5.186 3.861 6.757l-8.659 8.659.665.665 8.808-8.808c1.378.774 2.932 1.18 4.512 1.18 5.056 0 9.216-4.16 9.216-9.215 0-5.056-4.16-9.216-9.216-9.216-1.464 0-2.907.349-4.209 1.017l-9.039-8.587c.797-.953 1.234-2.156 1.234-3.399 0-2.906-2.391-5.297-5.297-5.297s-5.297 2.391-5.297 5.297 2.391 5.297 5.297 5.297c1.242 0 2.445-.436 3.397-1.233l8.86 8.417c-2.542 1.666-4.102 4.486-4.162 7.525h-1.987l-3.594-10.267-3.992 9.915h-4.457l-3.788 8.766-3.341-8.353-.01.004v-.09h-3.378c-.124-2.816-2.474-5.064-5.292-5.064-2.907 0-5.298 2.391-5.298 5.298 0 2.906 2.391 5.298 5.298 5.298 2.64 0 4.898-1.975 5.25-4.592h2.759l3.978 9.947 4.44-10.274zm18.152-8.685c4.54 0 8.276 3.736 8.276 8.276s-3.736 8.276-8.276 8.276-8.276-3.736-8.276-8.276c.005-4.538 3.737-8.271 8.276-8.276z" fill-rule="nonzero"/></g></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="2.41 2.16 355.42 355.17"><path fill="#fff" d="M9.49808 9.14822v341.59786h241.93485a99.66306 99.66306 0 0 0 99.657-99.663V9.14822zm266.263 215.32837h12.75033v12.75018h-12.75035zm-4.00963-54.89h20.71531v20.72131h-20.71533zm-3.186-54.10261h27.09335v27.11729h-27.09343zm-43.739 159.90321h12.7381v12.75018h-12.75017zm-3.98558-55.53932h20.72129V240.587h-20.72129zm-3.186-53.44731h27.12344v27.09337h-27.09339zm3.186-27.00322v-20.71528h20.72129v20.71527zm-97.82954 135.98981h12.75021v12.75018h-12.75021zm-3.98558-54.8901h20.72131v20.69121h-20.72124zm3.98558-34.1748v-12.75019h12.75021v12.75018zm-3.98558-67.64019h20.72131v20.71527h-20.72124zM72.10081 275.38715H84.875v12.75018H72.10081zm0-50.91056H84.875v12.75018H72.10081zm-3.97955-54.89h20.7153v20.72131h-20.7153zm-3.19206-54.10265h27.09939v27.11729H64.9292zM53.01459 52.67679h254.53469v50.91052H205.74622v203.6302h-50.90449v-203.6302H53.01459z"/></svg>

Before

Width:  |  Height:  |  Size: 958 B

@@ -1,7 +0,0 @@
import { plugin as PrometheusDatasourcePlugin } from './module';
describe('module', () => {
it('should have metrics query field in panels and Explore', () => {
expect(PrometheusDatasourcePlugin.components.QueryEditor).toBeDefined();
});
});
@@ -1,9 +0,0 @@
import { DataSourcePlugin } from '@grafana/data';
import { PromCheatSheet, PrometheusDatasource, PromQueryEditorByApp } from '@grafana/prometheus';
import { ConfigEditor } from './configuration/ConfigEditor';
export const plugin = new DataSourcePlugin(PrometheusDatasource)
.setQueryEditor(PromQueryEditorByApp)
.setConfigEditor(ConfigEditor)
.setQueryEditorHelp(PromCheatSheet);
@@ -1,102 +0,0 @@
{
"type": "datasource",
"name": "Prometheus",
"id": "prometheus",
"category": "tsdb",
"routes": [
{
"method": "POST",
"path": "api/v1/query",
"reqRole": "Viewer",
"reqAction": "datasources:query"
},
{
"method": "POST",
"path": "api/v1/query_range",
"reqRole": "Viewer",
"reqAction": "datasources:query"
},
{
"method": "POST",
"path": "api/v1/series",
"reqRole": "Viewer",
"reqAction": "datasources:query"
},
{
"method": "POST",
"path": "api/v1/labels",
"reqRole": "Viewer",
"reqAction": "datasources:query"
},
{
"method": "POST",
"path": "api/v1/query_exemplars",
"reqRole": "Viewer",
"reqAction": "datasources:query"
},
{
"method": "GET",
"path": "/rules",
"reqRole": "Viewer",
"reqAction": "alert.rules.external:read"
},
{
"method": "POST",
"path": "/rules",
"reqRole": "Editor",
"reqAction": "alert.rules.external:write"
},
{
"method": "DELETE",
"path": "/rules",
"reqRole": "Editor",
"reqAction": "alert.rules.external:write"
},
{
"method": "DELETE",
"path": "/config/v1/rules",
"reqRole": "Editor",
"reqAction": "alert.rules.external:write"
},
{
"method": "POST",
"path": "/config/v1/rules",
"reqRole": "Editor",
"reqAction": "alert.rules.external:write"
}
],
"includes": [],
"metrics": true,
"alerting": true,
"annotations": true,
"backend": true,
"queryOptions": {
"minInterval": true
},
"multiValueFilterOperators": true,
"info": {
"description": "Open source time series database & alerting",
"author": {
"name": "Grafana Labs",
"url": "https://grafana.com"
},
"logos": {
"small": "img/prometheus_logo.svg",
"large": "img/prometheus_logo.svg"
},
"links": [
{
"name": "Learn more",
"url": "https://prometheus.io/"
},
{
"name": "Raise issue",
"url": "https://github.com/grafana/grafana/issues/new"
},
{
"name": "Documentation",
"url": "https://grafana.com/docs/grafana/latest/datasources/prometheus/"
}
]
}
}