From 5647a981ed99aee02c0477527b1bd92e02a631cf Mon Sep 17 00:00:00 2001 From: Galen Kistler <109082771+gtk-grafana@users.noreply.github.com> Date: Wed, 26 Oct 2022 08:10:05 -0500 Subject: [PATCH] Prometheus: Flavor/version configuration (#57554) (#57563) * Backport PR for: Prometheus: Flavor/version configuration (#57554), and /label match parameter support #56510 * Revert "Revert "Prometheus: Type and flavor configuration (#56496)" (#57552)" This reverts commit 2432ce619a7e62026685df14f60116ea4fb3447d. * Adds new fields and documentation for Prometheus datasource configuration: prometheus type, and version (cherry picked from commit f93c3acc51180cee78222f2df95ac7b380d1a616) * Revert "Revert "Prometheus: Provide label values match parameter API when supported prometheus instance is configured (#56510)" (#57551)" (#57553) (#57564) This reverts commit e7671bf9092811ecb053466e5b9c9913e2922fe1. (cherry picked from commit e59ddd6bc5630625e49cafc965764cd8faa23cd6) Co-authored-by: Galen Kistler <109082771+gtk-grafana@users.noreply.github.com> Co-authored-by: Grot (@grafanabot) <43478413+grafanabot@users.noreply.github.com> --- devenv/datasources.yaml | 2 + .../administration/provisioning/index.md | 4 +- docs/sources/datasources/prometheus.md | 56 +++--- docs/sources/whatsnew/whats-new-in-v9-2.md | 7 + e2e/various-suite/exemplars.spec.ts | 2 +- pkg/tsdb/prometheus/prometheus.go | 31 ++- pkg/tsdb/prometheus/resource/resource.go | 20 ++ .../alerting/unified/RuleEditor.test.tsx | 10 +- .../alerting/unified/RuleList.test.tsx | 10 +- .../alerting/unified/api/buildInfo.test.ts | 6 +- .../alerting/unified/api/buildInfo.ts | 2 +- .../alerting/unified/state/actions.ts | 2 +- .../datasources/components/EditDataSource.tsx | 2 +- .../app/features/datasources/state/actions.ts | 6 +- .../configuration/PromFlavorVersions.ts | 80 ++++++++ .../configuration/PromSettings.test.tsx | 18 +- .../prometheus/configuration/PromSettings.tsx | 188 +++++++++++++++++- .../datasource/prometheus/datasource.tsx | 54 ++++- .../datasource/prometheus/img/thanos_logo.svg | 1 + .../prometheus/metric_find_query.ts | 19 +- .../plugins/datasource/prometheus/types.ts | 4 + public/app/types/unified-alerting-dto.ts | 4 +- 22 files changed, 437 insertions(+), 91 deletions(-) create mode 100644 public/app/plugins/datasource/prometheus/configuration/PromFlavorVersions.ts create mode 100644 public/app/plugins/datasource/prometheus/img/thanos_logo.svg diff --git a/devenv/datasources.yaml b/devenv/datasources.yaml index 43cec0e037b..37c4911e443 100644 --- a/devenv/datasources.yaml +++ b/devenv/datasources.yaml @@ -54,6 +54,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 5d3fc3e2f03..62ae3a1f109 100644 --- a/docs/sources/administration/provisioning/index.md +++ b/docs/sources/administration/provisioning/index.md @@ -157,7 +157,7 @@ Since not all datasources have the same configuration settings we only have the | httpMethod | string | Prometheus | HTTP Method. 'GET', 'POST', defaults to POST | | customQueryParameters | string | Prometheus | Query parameters to add, as a URL-encoded string. | | manageAlerts | boolean | Prometheus and Loki | Manage alerts via Alerting UI | -| 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 | @@ -189,6 +189,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/docs/sources/whatsnew/whats-new-in-v9-2.md b/docs/sources/whatsnew/whats-new-in-v9-2.md index 5ecf3788999..df46a65e4f9 100644 --- a/docs/sources/whatsnew/whats-new-in-v9-2.md +++ b/docs/sources/whatsnew/whats-new-in-v9-2.md @@ -219,3 +219,10 @@ For more information, see the [SAML configuration documentation](https://grafana You can now map OAuth groups and roles to Server Admin for the GitLab, GitHub, AzureAD, Okta, and Generic OAuth integrations. To enable this functionality, set the `allow_assign_grafana_admin` configuration option to `true` in the desired OAuth integration section. For more information, see the [authentication configuration documentation](https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/) for each OAuth client. + +## Match parameter support in prometheus labels API + +Prometheus users running Prometheus v2.24 and higher can use the [labels endpoint](https://prometheus.io/docs/prometheus/latest/querying/api/#querying-label-values) instead of the [series endpoint](https://prometheus.io/docs/prometheus/latest/querying/api/#finding-series-by-label-matchers) for the [`label_values` function]({{< relref "../datasources/prometheus/#query-variable" >}}). +This decreases load times for templated high-cardinality Prometheus instances. + +If you want to benefit from this endpoint you must first configure the Prometheus type and version in any Prometheus data sources' [configuration]({{< relref "../datasources/prometheus/" >}}). 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/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 3a6f11a16e8..0bd02af6cd7 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 1e0cdc8ffce..c05cb8cd785 100644 --- a/public/app/features/alerting/unified/RuleList.test.tsx +++ b/public/app/features/alerting/unified/RuleList.test.tsx @@ -226,7 +226,7 @@ describe('RuleList', () => { setDataSourceSrv(new MockDataSourceSrv({ prom: dataSources.prom })); mocks.api.discoverFeatures.mockResolvedValue({ - application: PromApplication.Lotex, + application: PromApplication.Cortex, features: { rulerApiEnabled: true, }, @@ -368,7 +368,7 @@ describe('RuleList', () => { setDataSourceSrv(new MockDataSourceSrv({ prom: dataSources.prom })); mocks.api.discoverFeatures.mockResolvedValue({ - application: PromApplication.Lotex, + application: PromApplication.Cortex, features: { rulerApiEnabled: true, }, @@ -516,7 +516,7 @@ describe('RuleList', () => { setDataSourceSrv(new MockDataSourceSrv(testDatasources)); mocks.api.discoverFeatures.mockResolvedValue({ - application: PromApplication.Lotex, + application: PromApplication.Cortex, features: { rulerApiEnabled: true, }, @@ -702,7 +702,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, }, @@ -729,7 +729,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 5cf40832d0f..118777aa6ce 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 1e2c66e1649..c3a21816e1b 100644 --- a/public/app/features/alerting/unified/state/actions.ts +++ b/public/app/features/alerting/unified/state/actions.ts @@ -265,7 +265,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 f8379fd7ae3..41e916947c9 100644 --- a/public/app/features/datasources/state/actions.ts +++ b/public/app/features/datasources/state/actions.ts @@ -17,7 +17,7 @@ import { DataSourcePluginCategory, ThunkDispatch, ThunkResult } from 'app/types' import * as api from '../api'; import { DATASOURCES_ROUTES } from '../constants'; -import { nameExits, findNewName } from '../utils'; +import { findNewName, nameExits } from '../utils'; import { buildCategories } from './buildCategories'; import { buildNavModel } from './navModel'; @@ -210,8 +210,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 583af4c28cf..203bff3ab79 100644 --- a/public/app/plugins/datasource/prometheus/configuration/PromSettings.tsx +++ b/public/app/plugins/datasource/prometheus/configuration/PromSettings.tsx @@ -1,23 +1,30 @@ 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, } 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 { Select, Input, FormField } = LegacyForms; const httpOptions = [ @@ -25,13 +32,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'; } @@ -39,6 +143,7 @@ export const PromSettings = (props: Props) => { return ( <>
+ {/* Scrape interval */}
{ />
+ {/* Query Timeout */}
{ />
+ {/* HTTP Method */}
- HTTP Method + HTTP method