diff --git a/devenv/datasources.yaml b/devenv/datasources.yaml index f552b608240..7046ae43137 100644 --- a/devenv/datasources.yaml +++ b/devenv/datasources.yaml @@ -59,6 +59,8 @@ datasources: jsonData: manageAlerts: true alertmanagerUid: gdev-alertmanager + prometheusType: Prometheus #Cortex | Mimir | Prometheus | Thanos + prometheusVersion: 2.40.0 - name: gdev-slow-prometheus type: prometheus diff --git a/docs/sources/administration/provisioning/index.md b/docs/sources/administration/provisioning/index.md index b76fbf8101b..c7f33080a76 100644 --- a/docs/sources/administration/provisioning/index.md +++ b/docs/sources/administration/provisioning/index.md @@ -158,7 +158,7 @@ Since not all datasources have the same configuration settings we only have the | customQueryParameters | string | Prometheus | Query parameters to add, as a URL-encoded string. | | manageAlerts | boolean | Prometheus and Loki | Manage alerts via Alerting UI | | alertmanagerUid | string | Prometheus and Loki | UID of Alert Manager that manages Alert for this data source. | -| esVersion | string | Elasticsearch | Elasticsearch version (E.g. `7.0.0`, `7.6.1`) | +| esVersion | string | Elasticsearch | Elasticsearch version (e.g. `7.0.0`, `7.6.1`) | | timeField | string | Elasticsearch | Which field that should be used as timestamp | | interval | string | Elasticsearch | Index date time format. nil(No Pattern), 'Hourly', 'Daily', 'Weekly', 'Monthly' or 'Yearly' | | logMessageField | string | Elasticsearch | Which field should be used as the log message | @@ -190,6 +190,8 @@ Since not all datasources have the same configuration settings we only have the | maxIdleConns | number | MySQL, PostgreSQL and MSSQL | Maximum number of connections in the idle connection pool (Grafana v5.4+) | | connMaxLifetime | number | MySQL, PostgreSQL and MSSQL | Maximum amount of time in seconds a connection may be reused (Grafana v5.4+) | | keepCookies | array | _HTTP\*_ | Cookies that needs to be passed along while communicating with datasources | +| prometheusVersion | string | Prometheus | The version of the Prometheus datasource (e.g. `2.37.0`, `2.24.0`) | +| prometheusType | string | Prometheus | The type of the Prometheus datasources (i.e. `Prometheus`, `Cortex`, `Thanos`, or `Mimir`) | #### Secure Json Data diff --git a/docs/sources/datasources/prometheus.md b/docs/sources/datasources/prometheus.md index b50073f5dae..bc2a4295c04 100644 --- a/docs/sources/datasources/prometheus.md +++ b/docs/sources/datasources/prometheus.md @@ -21,25 +21,27 @@ Grafana includes built-in support for Prometheus. This topic explains options, v To access Prometheus settings, hover your mouse over the **Configuration** (gear) icon, then click **Data Sources**, and then click the Prometheus data source. -| Name | Description | -| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Name` | The data source name. This is how you refer to the data source in panels and queries. | -| `Default` | Default data source that is pre-selected for new panels. | -| `Url` | The URL of your Prometheus server, for example, `http://prometheus.example.org:9090`. | -| `Access` | Only Server access mode is functional. If Server mode is already selected this option is hidden. Otherwise change to Server mode to prevent errors. | -| `Basic Auth` | Enable basic authentication to the Prometheus data source. | -| `User` | User name for basic authentication. | -| `Password` | Password for basic authentication. | -| `Scrape interval` | Set this to the typical scrape and evaluation interval configured in Prometheus. Defaults to 15s. | -| `HTTP method` | Use either POST or GET HTTP method to query your data source. POST is the recommended and pre-selected method as it allows bigger queries. Change this to GET if you have a Prometheus version older than 2.1 or if POST requests are restricted in your network. | -| `Disable metrics lookup` | Checking this option will disable the metrics chooser and metric/label support in the query field's autocomplete. This helps if you have performance issues with bigger Prometheus instances. | -| `Custom Query Parameters` | Add custom parameters to the Prometheus query URL. For example `timeout`, `partial_response`, `dedup`, or `max_source_resolution`. Multiple parameters should be concatenated together with an '&'. | -| **Exemplars configuration** | | -| `Internal link` | Enable this option is you have an internal link. When you enable this option, you will see a data source selector. Select the backend tracing data store for your exemplar data. | -| `Data source` | You will see this option only if you enable `Internal link` option. Select the backend tracing data store for your exemplar data. | -| `URL` | You will see this option only if the `Internal link` option is disabled. Enter the full URL of the external link. You can interpolate the value from the field with `${__value.raw }` macro. | -| `URL Label` | (Optional) add a custom display label to override the value of the `Label name` field. | -| `Label name` | Add a name for the exemplar traceID property. | +| Name | Description | +| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Name` | The data source name. This is how you refer to the data source in panels and queries. | +| `Default` | Default data source that is pre-selected for new panels. | +| `Url` | The URL of your Prometheus server, for example, `http://prometheus.example.org:9090`. | +| `Access` | Only Server access mode is functional. If Server mode is already selected this option is hidden. Otherwise change to Server mode to prevent errors. | +| `Basic Auth` | Enable basic authentication to the Prometheus data source. | +| `User` | User name for basic authentication. | +| `Password` | Password for basic authentication. | +| `Scrape interval` | Set this to the typical scrape and evaluation interval configured in Prometheus. Defaults to 15s. | +| `HTTP method` | Use either POST or GET HTTP method to query your data source. POST is the recommended and pre-selected method as it allows bigger queries. Change this to GET if you have a Prometheus version older than 2.1 or if POST requests are restricted in your network. | +| `Type` | The type of your Prometheus server, i.e `Prometheus`, `Cortex`, `Thanos` or `Mimir`. When this value is selected in the configuration UI, the Prometheus version field attempts to detect the version automatically using the Prometheus [buildinfo](https://semver.org/) API. Some Prometheus types do not support this API, and you will need to manually populate the version in those cases (e.g. Cortex). | +| `Version` | The version of your Prometheus server, note that this field is not visible until the Prometheus type is selected. | +| `Disable metrics lookup` | Checking this option will disable the metrics chooser and metric/label support in the query field's autocomplete. This helps if you have performance issues with bigger Prometheus instances. | +| `Custom Query Parameters` | Add custom parameters to the Prometheus query URL. For example `timeout`, `partial_response`, `dedup`, or `max_source_resolution`. Multiple parameters should be concatenated together with an '&'. | +| **Exemplars configuration** | | +| `Internal link` | Enable this option is you have an internal link. When you enable this option, you will see a data source selector. Select the backend tracing data store for your exemplar data. | +| `Data source` | You will see this option only if you enable `Internal link` option. Select the backend tracing data store for your exemplar data. | +| `URL` | You will see this option only if the `Internal link` option is disabled. Enter the full URL of the external link. You can interpolate the value from the field with `${__value.raw }` macro. | +| `URL Label` | (Optional) add a custom display label to override the value of the `Label name` field. | +| `Label name` | Add a name for the exemplar traceID property. | ## Prometheus query editor @@ -176,13 +178,13 @@ types of template variables. Variable of the type _Query_ allows you to query Prometheus for a list of metrics, labels or label values. The Prometheus data source plugin provides the following functions you can use in the `Query` input field. -| Name | Description | Used API endpoints | -| ----------------------------- | ----------------------------------------------------------------------- | --------------------------------- | -| `label_names()` | Returns a list of label names. | /api/v1/labels | -| `label_values(label)` | Returns a list of label values for the `label` in every metric. | /api/v1/label/`label`/values | -| `label_values(metric, label)` | Returns a list of label values for the `label` in the specified metric. | /api/v1/series | -| `metrics(metric)` | Returns a list of metrics matching the specified `metric` regex. | /api/v1/label/\_\_name\_\_/values | -| `query_result(query)` | Returns a list of Prometheus query result for the `query`. | /api/v1/query | +| Name | Description | Used API endpoints | +| ----------------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | +| `label_names()` | Returns a list of label names. | /api/v1/labels | +| `label_values(label)` | Returns a list of label values for the `label` in every metric. | /api/v1/label/`label`/values | +| `label_values(metric, label)` | Returns a list of label values for the `label` in the specified metric. | /api/v1/series or /api/v1/label/`label`/values, depending on prometheus type and version in datasource configuration | +| `metrics(metric)` | Returns a list of metrics matching the specified `metric` regex. | /api/v1/label/\_\_name\_\_/values | +| `query_result(query)` | Returns a list of Prometheus query result for the `query`. | /api/v1/query | For details of what _metric names_, _label names_ and _label values_ are please refer to the [Prometheus documentation](http://prometheus.io/docs/concepts/data_model/#metric-names-and-labels). @@ -282,6 +284,8 @@ datasources: url: http://localhost:9090 jsonData: httpMethod: POST + prometheusType: Prometheus + prometheusVersion: 2.37.0 exemplarTraceIdDestinations: # Field with internal link pointing to data source in Grafana. # datasourceUid value can be anything, but it should be unique across all defined data source uids. diff --git a/e2e/various-suite/exemplars.spec.ts b/e2e/various-suite/exemplars.spec.ts index 0577b6c5add..fa07c4755d6 100644 --- a/e2e/various-suite/exemplars.spec.ts +++ b/e2e/various-suite/exemplars.spec.ts @@ -10,7 +10,7 @@ const addDataSource = () => { e2e.components.DataSource.Prometheus.configPage.exemplarsAddButton().click(); e2e.components.DataSource.Prometheus.configPage.internalLinkSwitch().check({ force: true }); e2e.components.DataSource.DataSourceHttpSettings.urlInput().type('http://prom-url:9090'); - e2e.components.DataSourcePicker.inputV2().should('be.visible').click({ force: true }); + e2e.components.DataSourcePicker.inputV2().click({ force: true }).should('have.focus'); e2e().contains('gdev-tempo').scrollIntoView().should('be.visible').click(); }, diff --git a/pkg/services/ngalert/eval/evaluator_mock.go b/pkg/services/ngalert/eval/evaluator_mock.go index db307508cb4..b26ef309b6d 100644 --- a/pkg/services/ngalert/eval/evaluator_mock.go +++ b/pkg/services/ngalert/eval/evaluator_mock.go @@ -46,8 +46,8 @@ type FakeEvaluator_ConditionEval_Call struct { } // ConditionEval is a helper method to define mock.On call -// - ctx EvaluationContext -// - condition models.Condition +// - ctx EvaluationContext +// - condition models.Condition func (_e *FakeEvaluator_Expecter) ConditionEval(ctx interface{}, condition interface{}) *FakeEvaluator_ConditionEval_Call { return &FakeEvaluator_ConditionEval_Call{Call: _e.mock.On("ConditionEval", ctx, condition)} } @@ -93,8 +93,8 @@ type FakeEvaluator_QueriesAndExpressionsEval_Call struct { } // QueriesAndExpressionsEval is a helper method to define mock.On call -// - ctx EvaluationContext -// - data []models.AlertQuery +// - ctx EvaluationContext +// - data []models.AlertQuery func (_e *FakeEvaluator_Expecter) QueriesAndExpressionsEval(ctx interface{}, data interface{}) *FakeEvaluator_QueriesAndExpressionsEval_Call { return &FakeEvaluator_QueriesAndExpressionsEval_Call{Call: _e.mock.On("QueriesAndExpressionsEval", ctx, data)} } @@ -131,8 +131,8 @@ type FakeEvaluator_Validate_Call struct { } // Validate is a helper method to define mock.On call -// - ctx EvaluationContext -// - condition models.Condition +// - ctx EvaluationContext +// - condition models.Condition func (_e *FakeEvaluator_Expecter) Validate(ctx interface{}, condition interface{}) *FakeEvaluator_Validate_Call { return &FakeEvaluator_Validate_Call{Call: _e.mock.On("Validate", ctx, condition)} } diff --git a/pkg/tsdb/prometheus/prometheus.go b/pkg/tsdb/prometheus/prometheus.go index 48d4b417c5f..55354a64450 100644 --- a/pkg/tsdb/prometheus/prometheus.go +++ b/pkg/tsdb/prometheus/prometheus.go @@ -6,7 +6,9 @@ import ( "errors" "fmt" "reflect" + "strings" "sync" + "time" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend/datasource" @@ -19,6 +21,7 @@ import ( "github.com/grafana/grafana/pkg/tsdb/prometheus/buffered" "github.com/grafana/grafana/pkg/tsdb/prometheus/querydata" "github.com/grafana/grafana/pkg/tsdb/prometheus/resource" + "github.com/patrickmn/go-cache" apiv1 "github.com/prometheus/client_golang/api/prometheus/v1" "github.com/yudai/gojsondiff" "github.com/yudai/gojsondiff/formatter" @@ -32,9 +35,10 @@ type Service struct { } type instance struct { - buffered *buffered.Buffered - queryData *querydata.QueryData - resource *resource.Resource + buffered *buffered.Buffered + queryData *querydata.QueryData + resource *resource.Resource + versionCache *cache.Cache } func ProvideService(httpClientProvider httpclient.Provider, cfg *setting.Cfg, features featuremgmt.FeatureToggles, tracer tracing.Tracer) *Service { @@ -75,9 +79,10 @@ func newInstanceSettings(httpClientProvider httpclient.Provider, cfg *setting.Cf } return instance{ - buffered: b, - queryData: qd, - resource: r, + buffered: b, + queryData: qd, + resource: r, + versionCache: cache.New(time.Minute*1, time.Minute*5), }, nil } } @@ -135,6 +140,20 @@ func (s *Service) CallResource(ctx context.Context, req *backend.CallResourceReq return err } + if strings.EqualFold(req.Path, "version-detect") { + versionObj, found := i.versionCache.Get("version") + if found { + return sender.Send(versionObj.(*backend.CallResourceResponse)) + } + + vResp, err := i.resource.DetectVersion(ctx, req) + if err != nil { + return err + } + i.versionCache.Set("version", vResp, cache.DefaultExpiration) + return sender.Send(vResp) + } + resp, err := i.resource.Execute(ctx, req) if err != nil { return err diff --git a/pkg/tsdb/prometheus/resource/resource.go b/pkg/tsdb/prometheus/resource/resource.go index bf69cd5d8c1..289c920ba0e 100644 --- a/pkg/tsdb/prometheus/resource/resource.go +++ b/pkg/tsdb/prometheus/resource/resource.go @@ -98,3 +98,23 @@ func (r *Resource) Execute(ctx context.Context, req *backend.CallResourceRequest return callResponse, err } + +func (r *Resource) DetectVersion(ctx context.Context, req *backend.CallResourceRequest) (*backend.CallResourceResponse, error) { + newReq := &backend.CallResourceRequest{ + PluginContext: req.PluginContext, + Path: "/api/v1/status/buildinfo", + } + + resp, err := r.Execute(ctx, newReq) + + if err != nil { + return nil, err + } + + callResponse := &backend.CallResourceResponse{ + Status: 200, + Body: resp.Body, + } + + return callResponse, nil +} diff --git a/public/app/features/alerting/unified/RuleEditor.test.tsx b/public/app/features/alerting/unified/RuleEditor.test.tsx index 734b6d7af52..3273427ce06 100644 --- a/public/app/features/alerting/unified/RuleEditor.test.tsx +++ b/public/app/features/alerting/unified/RuleEditor.test.tsx @@ -146,7 +146,7 @@ describe('RuleEditor', () => { mocks.searchFolders.mockResolvedValue([]); mocks.api.discoverFeatures.mockResolvedValue({ - application: PromApplication.Lotex, + application: PromApplication.Cortex, features: { rulerApiEnabled: true, }, @@ -345,7 +345,7 @@ describe('RuleEditor', () => { mocks.searchFolders.mockResolvedValue([]); mocks.api.discoverFeatures.mockResolvedValue({ - application: PromApplication.Lotex, + application: PromApplication.Cortex, features: { rulerApiEnabled: true, }, @@ -598,7 +598,7 @@ describe('RuleEditor', () => { mocks.api.discoverFeatures.mockImplementation(async (dataSourceName) => { if (dataSourceName === 'loki with ruler' || dataSourceName === 'cortex with ruler') { return { - application: PromApplication.Lotex, + application: PromApplication.Cortex, features: { rulerApiEnabled: true, alertManagerConfigApi: false, @@ -609,7 +609,7 @@ describe('RuleEditor', () => { } if (dataSourceName === 'loki with local rule store') { return { - application: PromApplication.Lotex, + application: PromApplication.Cortex, features: { rulerApiEnabled: false, alertManagerConfigApi: false, @@ -620,7 +620,7 @@ describe('RuleEditor', () => { } if (dataSourceName === 'cortex without ruler api') { return { - application: PromApplication.Lotex, + application: PromApplication.Cortex, features: { rulerApiEnabled: false, alertManagerConfigApi: false, diff --git a/public/app/features/alerting/unified/RuleList.test.tsx b/public/app/features/alerting/unified/RuleList.test.tsx index 66fd221e9cf..76be42c9363 100644 --- a/public/app/features/alerting/unified/RuleList.test.tsx +++ b/public/app/features/alerting/unified/RuleList.test.tsx @@ -234,7 +234,7 @@ describe('RuleList', () => { setDataSourceSrv(new MockDataSourceSrv({ prom: dataSources.prom })); mocks.api.discoverFeatures.mockResolvedValue({ - application: PromApplication.Lotex, + application: PromApplication.Cortex, features: { rulerApiEnabled: true, }, @@ -376,7 +376,7 @@ describe('RuleList', () => { setDataSourceSrv(new MockDataSourceSrv({ prom: dataSources.prom })); mocks.api.discoverFeatures.mockResolvedValue({ - application: PromApplication.Lotex, + application: PromApplication.Cortex, features: { rulerApiEnabled: true, }, @@ -524,7 +524,7 @@ describe('RuleList', () => { setDataSourceSrv(new MockDataSourceSrv(testDatasources)); mocks.api.discoverFeatures.mockResolvedValue({ - application: PromApplication.Lotex, + application: PromApplication.Cortex, features: { rulerApiEnabled: true, }, @@ -710,7 +710,7 @@ describe('RuleList', () => { mocks.getAllDataSourcesMock.mockReturnValue([dataSources.prom]); setDataSourceSrv(new MockDataSourceSrv({ prom: dataSources.prom })); mocks.api.discoverFeatures.mockResolvedValue({ - application: PromApplication.Lotex, + application: PromApplication.Cortex, features: { rulerApiEnabled: true, }, @@ -737,7 +737,7 @@ describe('RuleList', () => { mocks.getAllDataSourcesMock.mockReturnValue([dataSources.prom]); setDataSourceSrv(new MockDataSourceSrv({ prom: dataSources.prom })); mocks.api.discoverFeatures.mockResolvedValue({ - application: PromApplication.Lotex, + application: PromApplication.Cortex, features: { rulerApiEnabled: true, }, diff --git a/public/app/features/alerting/unified/api/buildInfo.test.ts b/public/app/features/alerting/unified/api/buildInfo.test.ts index b01af7f763e..5bc0e7f60a2 100644 --- a/public/app/features/alerting/unified/api/buildInfo.test.ts +++ b/public/app/features/alerting/unified/api/buildInfo.test.ts @@ -93,7 +93,7 @@ describe('discoverDataSourceFeatures', () => { const response = await discoverDataSourceFeatures({ url: '/datasource/proxy', name: 'Loki', type: 'loki' }); - expect(response.application).toBe(PromApplication.Lotex); + expect(response.application).toBe(PromApplication.Cortex); expect(response.features.rulerApiEnabled).toBe(true); expect(mocks.fetchTestRulerRulesGroup).toHaveBeenCalledTimes(1); @@ -126,7 +126,7 @@ describe('discoverDataSourceFeatures', () => { type: 'prometheus', }); - expect(response.application).toBe(PromApplication.Lotex); + expect(response.application).toBe(PromApplication.Cortex); expect(response.features.rulerApiEnabled).toBe(false); expect(mocks.fetchTestRulerRulesGroup).toHaveBeenCalledTimes(1); @@ -152,7 +152,7 @@ describe('discoverDataSourceFeatures', () => { type: 'prometheus', }); - expect(response.application).toBe(PromApplication.Lotex); + expect(response.application).toBe(PromApplication.Cortex); expect(response.features.rulerApiEnabled).toBe(true); expect(mocks.fetchTestRulerRulesGroup).toHaveBeenCalledTimes(1); diff --git a/public/app/features/alerting/unified/api/buildInfo.ts b/public/app/features/alerting/unified/api/buildInfo.ts index 25673c4c0f4..3504a5d67a1 100644 --- a/public/app/features/alerting/unified/api/buildInfo.ts +++ b/public/app/features/alerting/unified/api/buildInfo.ts @@ -78,7 +78,7 @@ export async function discoverDataSourceFeatures(dsSettings: { const rulerSupported = await hasRulerSupport(name); return { - application: PromApplication.Lotex, + application: PromApplication.Cortex, features: { rulerApiEnabled: rulerSupported, }, diff --git a/public/app/features/alerting/unified/state/actions.ts b/public/app/features/alerting/unified/state/actions.ts index 8fcfe09d197..c5c9b2a312d 100644 --- a/public/app/features/alerting/unified/state/actions.ts +++ b/public/app/features/alerting/unified/state/actions.ts @@ -266,7 +266,7 @@ export const fetchRulesSourceBuildInfoAction = createAsyncThunk( const rulerConfig: RulerDataSourceConfig | undefined = buildInfo.features.rulerApiEnabled ? { dataSourceName: name, - apiVersion: buildInfo.application === PromApplication.Lotex ? 'legacy' : 'config', + apiVersion: buildInfo.application === PromApplication.Cortex ? 'legacy' : 'config', } : undefined; diff --git a/public/app/features/datasources/components/EditDataSource.tsx b/public/app/features/datasources/components/EditDataSource.tsx index d5d764db2f8..6f65256ca7a 100644 --- a/public/app/features/datasources/components/EditDataSource.tsx +++ b/public/app/features/datasources/components/EditDataSource.tsx @@ -86,7 +86,7 @@ export type ViewProps = { onNameChange: (name: string) => AnyAction; onOptionsChange: (dataSource: DataSourceSettingsType) => AnyAction; onTest: () => void; - onUpdate: (dataSource: DataSourceSettingsType) => Promise; + onUpdate: (dataSource: DataSourceSettingsType) => Promise; }; export function EditDataSourceView({ diff --git a/public/app/features/datasources/state/actions.ts b/public/app/features/datasources/state/actions.ts index 800acbf0277..0ce72e58b28 100644 --- a/public/app/features/datasources/state/actions.ts +++ b/public/app/features/datasources/state/actions.ts @@ -19,7 +19,7 @@ import { DataSourcePluginCategory, ThunkDispatch, ThunkResult } from 'app/types' import * as api from '../api'; import { DATASOURCES_ROUTES } from '../constants'; import { trackDataSourceCreated, trackDataSourceTested } from '../tracking'; -import { nameExits, findNewName } from '../utils'; +import { findNewName, nameExits } from '../utils'; import { buildCategories } from './buildCategories'; import { buildNavModel } from './navModel'; @@ -231,8 +231,8 @@ export function loadDataSourcePlugins(): ThunkResult { }; } -export function updateDataSource(dataSource: DataSourceSettings): ThunkResult { - return async (dispatch) => { +export function updateDataSource(dataSource: DataSourceSettings) { + return async (dispatch: (dataSourceSettings: ThunkResult>) => DataSourceSettings) => { await api.updateDataSource(dataSource); await getDatasourceSrv().reload(); return dispatch(loadDataSource(dataSource.uid)); diff --git a/public/app/plugins/datasource/prometheus/configuration/PromFlavorVersions.ts b/public/app/plugins/datasource/prometheus/configuration/PromFlavorVersions.ts new file mode 100644 index 00000000000..eb113762df4 --- /dev/null +++ b/public/app/plugins/datasource/prometheus/configuration/PromFlavorVersions.ts @@ -0,0 +1,80 @@ +export const PromFlavorVersions: { [index: string]: Array<{ value?: string; label: string }> } = { + Prometheus: [ + { value: undefined, label: 'Please select' }, + { value: '2.0.0', label: '< 2.14.x' }, + { value: '2.14.0', label: '2.14.x' }, + { value: '2.15.0', label: '2.15.x' }, + { value: '2.16.0', label: '2.16.x' }, + { value: '2.17.0', label: '2.17.x' }, + { value: '2.18.0', label: '2.18.x' }, + { value: '2.19.0', label: '2.19.x' }, + { value: '2.20.0', label: '2.20.x' }, + { value: '2.21.0', label: '2.21.x' }, + { value: '2.22.0', label: '2.22.x' }, + { value: '2.23.0', label: '2.23.x' }, + { value: '2.24.0', label: '2.24.x' }, + { value: '2.25.0', label: '2.25.x' }, + { value: '2.26.0', label: '2.26.x' }, + { value: '2.27.0', label: '2.27.x' }, + { value: '2.28.0', label: '2.28.x' }, + { value: '2.29.0', label: '2.29.x' }, + { value: '2.30.0', label: '2.30.x' }, + { value: '2.31.0', label: '2.31.x' }, + { value: '2.32.0', label: '2.32.x' }, + { value: '2.33.0', label: '2.33.x' }, + { value: '2.34.0', label: '2.34.x' }, + { value: '2.35.0', label: '2.35.x' }, + { value: '2.36.0', label: '2.36.x' }, + { value: '2.37.0', label: '2.37.x' }, + { value: '2.38.0', label: '2.38.x' }, + { value: '2.39.0', label: '2.39.x' }, + { value: '2.40.0', label: '2.40.x' }, + + // This value will be returned for future versions of prometheus until we add new entries to this object + { value: '2.40.1', label: '> 2.40.x' }, + ], + Mimir: [ + { value: undefined, label: 'Please select' }, + { value: '2.0.0', label: '2.0.x' }, + { value: '2.1.0', label: '2.1.x' }, + { value: '2.2.0', label: '2.2.x' }, + { value: '2.3.0', label: '2.3.x' }, + { value: '2.4.0', label: '> 2.3.x' }, + ], + Thanos: [ + { value: undefined, label: 'Please select' }, + { value: '0.0.0', label: '< 0.16.x' }, + { value: '0.16.0', label: '0.16.x' }, + { value: '0.17.0', label: '0.17.x' }, + { value: '0.18.0', label: '0.18.x' }, + { value: '0.19.0', label: '0.19.x' }, + { value: '0.20.0', label: '0.20.x' }, + { value: '0.21.0', label: '0.21.x' }, + { value: '0.22.0', label: '0.22.x' }, + { value: '0.23.0', label: '0.23.x' }, + { value: '0.24.0', label: '0.24.x' }, + { value: '0.25.0', label: '0.25.x' }, + { value: '0.26.0', label: '0.26.x' }, + { value: '0.27.0', label: '0.27.x' }, + { value: '0.28.0', label: '0.28.x' }, + { value: '0.29.0', label: '> 0.28.x' }, + ], + Cortex: [ + { value: undefined, label: 'Please select' }, + { value: '0.0.0', label: '< 1.0.0' }, + { value: '1.0.0', label: '1.0.0' }, + { value: '1.1.0', label: '1.1.x' }, + { value: '1.2.0', label: '1.2.x' }, + { value: '1.3.0', label: '1.3.x' }, + { value: '1.4.0', label: '1.4.x' }, + { value: '1.5.0', label: '1.5.x' }, + { value: '1.6.0', label: '1.6.x' }, + { value: '1.7.0', label: '1.7.x' }, + { value: '1.8.0', label: '1.8.x' }, + { value: '1.9.0', label: '1.9.x' }, + { value: '1.10.0', label: '1.10.x' }, + { value: '1.11.0', label: '1.11.x' }, + { value: '1.13.0', label: '1.13.x' }, + { value: '1.14.0', label: '> 1.13.x' }, + ], +}; diff --git a/public/app/plugins/datasource/prometheus/configuration/PromSettings.test.tsx b/public/app/plugins/datasource/prometheus/configuration/PromSettings.test.tsx index 0cb1cb7bfab..bf91f3d5be8 100644 --- a/public/app/plugins/datasource/prometheus/configuration/PromSettings.test.tsx +++ b/public/app/plugins/datasource/prometheus/configuration/PromSettings.test.tsx @@ -1,9 +1,12 @@ import { render, screen } from '@testing-library/react'; import React, { SyntheticEvent } from 'react'; +import { Provider } from 'react-redux'; import { SelectableValue } from '@grafana/data'; import { EventsWithValidation } from '@grafana/ui'; +import { configureStore } from '../../../../store/configureStore'; + import { getValueFromEventItem, promSettingsValidationEvents, PromSettings } from './PromSettings'; import { createDefaultConfigOptions } from './mocks'; @@ -94,11 +97,12 @@ describe('PromSettings', () => { const options = defaultProps; options.url = ''; options.jsonData.httpMethod = ''; + const store = configureStore(); render( -
+ {}} options={options} /> -
+ ); expect(screen.getByText('POST')).toBeInTheDocument(); }); @@ -106,11 +110,12 @@ describe('PromSettings', () => { const options = defaultProps; options.url = 'test_url'; options.jsonData.httpMethod = 'POST'; + const store = configureStore(); render( -
+ {}} options={options} /> -
+ ); expect(screen.getByText('POST')).toBeInTheDocument(); }); @@ -118,11 +123,12 @@ describe('PromSettings', () => { const options = defaultProps; options.url = 'test_url'; options.jsonData.httpMethod = 'GET'; + const store = configureStore(); render( -
+ {}} options={options} /> -
+ ); expect(screen.getByText('GET')).toBeInTheDocument(); }); diff --git a/public/app/plugins/datasource/prometheus/configuration/PromSettings.tsx b/public/app/plugins/datasource/prometheus/configuration/PromSettings.tsx index fdddfb17c90..de50b5ca5ba 100644 --- a/public/app/plugins/datasource/prometheus/configuration/PromSettings.tsx +++ b/public/app/plugins/datasource/prometheus/configuration/PromSettings.tsx @@ -1,24 +1,31 @@ import React, { SyntheticEvent } from 'react'; +import semver from 'semver/preload'; import { DataSourcePluginOptionsEditorProps, + DataSourceSettings as DataSourceSettingsType, onUpdateDatasourceJsonDataOptionChecked, SelectableValue, updateDatasourcePluginJsonDataOption, } from '@grafana/data'; +import { getBackendSrv } from '@grafana/runtime/src'; import { - InlineField, - InlineSwitch, EventsWithValidation, + InlineField, InlineFormLabel, + InlineSwitch, LegacyForms, regexValidation, Select, } from '@grafana/ui'; +import { useUpdateDatasource } from '../../../../features/datasources/state'; +import { PromApplication, PromBuildInfoResponse } from '../../../../types/unified-alerting-dto'; import { PromOptions } from '../types'; import { ExemplarsSettings } from './ExemplarsSettings'; +import { PromFlavorVersions } from './PromFlavorVersions'; + const { Input, FormField } = LegacyForms; const httpOptions = [ @@ -26,13 +33,110 @@ const httpOptions = [ { value: 'GET', label: 'GET' }, ]; +type PrometheusSelectItemsType = Array<{ value: PromApplication; label: PromApplication }>; + +const prometheusFlavorSelectItems: PrometheusSelectItemsType = [ + { value: PromApplication.Prometheus, label: PromApplication.Prometheus }, + { value: PromApplication.Cortex, label: PromApplication.Cortex }, + { value: PromApplication.Mimir, label: PromApplication.Mimir }, + { value: PromApplication.Thanos, label: PromApplication.Thanos }, +]; + type Props = Pick, 'options' | 'onOptionsChange'>; +/** + * Returns the closest version to what the user provided that we have in our PromFlavorVersions for the currently selected flavor + * Bugs: It will only reject versions that are a major release apart, so Mimir 2.x might get selected for Prometheus 2.8 if the user selects an incorrect flavor + * Advantages: We don't need to maintain a list of every possible version for each release + * + * This function will return the closest version from PromFlavorVersions that is equal or lower to the version argument, + * unless the versions are a major release apart. + */ +const getVersionString = (version: string, flavor?: string): string | undefined => { + if (!flavor || !PromFlavorVersions[flavor]) { + return; + } + const flavorVersionValues = PromFlavorVersions[flavor]; + + // As long as it's assured we're using versions which are sorted, we could just filter out the values greater than the target version, and then check the last element in the array + const versionsLessThanOrEqual = flavorVersionValues + ?.filter((el) => !!el.value && semver.lte(el.value, version)) + .map((el) => el.value); + + const closestVersion = versionsLessThanOrEqual[versionsLessThanOrEqual.length - 1]; + + if (closestVersion) { + const differenceBetweenActualAndClosest = semver.diff(closestVersion, version); + + // Only return versions if the target is close to the actual. + if (['patch', 'prepatch', 'prerelease', null].includes(differenceBetweenActualAndClosest)) { + return closestVersion; + } + } + + return; +}; + +const unableToDeterminePrometheusVersion = (error?: Error): void => { + console.warn('Error fetching version from buildinfo API, must manually select version!', error); +}; + +/** + * I don't like the daisy chain of network requests, and that we have to save on behalf of the user, but currently + * the backend doesn't allow for the prometheus client url to be passed in from the frontend, so we currently need to save it + * to the database before consumption. + * + * Since the prometheus version fields are below the url field, we can expect users to populate this field before + * hitting save and test at the bottom of the page. For this case we need to save the current fields before calling the + * resource to auto-detect the version. + * + * @param options + * @param onOptionsChange + * @param onUpdate + */ +const setPrometheusVersion = ( + options: DataSourceSettingsType, + onOptionsChange: (options: DataSourceSettingsType) => void, + onUpdate: (dataSource: DataSourceSettingsType) => Promise> +) => { + // This will save the current state of the form, as the url is needed for this API call to function + onUpdate(options) + .then((updatedOptions) => { + getBackendSrv() + .get(`/api/datasources/${updatedOptions.id}/resources/version-detect`) + .then((rawResponse: PromBuildInfoResponse) => { + const rawVersionStringFromApi = rawResponse.data?.version ?? ''; + if (rawVersionStringFromApi && semver.valid(rawVersionStringFromApi)) { + const parsedVersion = getVersionString(rawVersionStringFromApi, updatedOptions.jsonData.prometheusType); + // If we got a successful response, let's update the backend with the version right away if it's new + if (parsedVersion) { + onUpdate({ + ...updatedOptions, + jsonData: { + ...updatedOptions.jsonData, + prometheusVersion: parsedVersion, + }, + }).then((updatedUpdatedOptions) => { + onOptionsChange(updatedUpdatedOptions); + }); + } + } else { + unableToDeterminePrometheusVersion(); + } + }); + }) + .catch((error) => { + unableToDeterminePrometheusVersion(error); + }); +}; + export const PromSettings = (props: Props) => { const { options, onOptionsChange } = props; - // We are explicitly adding httpMethod so it is correctly displayed in dropdown. This way, it is more predictable for users. + // This update call is typed as void, but it returns a response which we need + const onUpdate = useUpdateDatasource(); + // We are explicitly adding httpMethod so it is correctly displayed in dropdown. This way, it is more predictable for users. if (!options.jsonData.httpMethod) { options.jsonData.httpMethod = 'POST'; } @@ -40,6 +144,7 @@ export const PromSettings = (props: Props) => { return ( <>
+ {/* Scrape interval */}
{ />
+ {/* Query Timeout */}
{ />
+ {/* HTTP Method */}
- HTTP Method + HTTP method