mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Prometheus: Flavor/version configuration (#57554)
* Revert "Revert "Prometheus: Type and flavor configuration (#56496)" (#57552)"
This reverts commit 2432ce619a
.
* Adds new fields and documentation for Prometheus datasource configuration: prometheus type, and version
This commit is contained in:
parent
cfd9e72da5
commit
f93c3acc51
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -22,7 +22,7 @@ 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`. |
|
||||
@ -32,6 +32,8 @@ To access Prometheus settings, hover your mouse over the **Configuration** (gear
|
||||
| `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** | |
|
||||
@ -177,10 +179,10 @@ Variable of the type _Query_ allows you to query Prometheus for a list of metric
|
||||
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 |
|
||||
| `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 |
|
||||
|
||||
@ -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.
|
||||
|
@ -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();
|
||||
},
|
||||
|
@ -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"
|
||||
@ -35,6 +38,7 @@ type instance struct {
|
||||
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 {
|
||||
@ -78,6 +82,7 @@ func newInstanceSettings(httpClientProvider httpclient.Provider, cfg *setting.Cf
|
||||
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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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);
|
||||
|
@ -78,7 +78,7 @@ export async function discoverDataSourceFeatures(dsSettings: {
|
||||
const rulerSupported = await hasRulerSupport(name);
|
||||
|
||||
return {
|
||||
application: PromApplication.Lotex,
|
||||
application: PromApplication.Cortex,
|
||||
features: {
|
||||
rulerApiEnabled: rulerSupported,
|
||||
},
|
||||
|
@ -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;
|
||||
|
||||
|
@ -86,7 +86,7 @@ export type ViewProps = {
|
||||
onNameChange: (name: string) => AnyAction;
|
||||
onOptionsChange: (dataSource: DataSourceSettingsType) => AnyAction;
|
||||
onTest: () => void;
|
||||
onUpdate: (dataSource: DataSourceSettingsType) => Promise<void>;
|
||||
onUpdate: (dataSource: DataSourceSettingsType) => Promise<DataSourceSettingsType>;
|
||||
};
|
||||
|
||||
export function EditDataSourceView({
|
||||
|
@ -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<void> {
|
||||
};
|
||||
}
|
||||
|
||||
export function updateDataSource(dataSource: DataSourceSettings): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
export function updateDataSource(dataSource: DataSourceSettings) {
|
||||
return async (dispatch: (dataSourceSettings: ThunkResult<Promise<DataSourceSettings>>) => DataSourceSettings) => {
|
||||
await api.updateDataSource(dataSource);
|
||||
await getDatasourceSrv().reload();
|
||||
return dispatch(loadDataSource(dataSource.uid));
|
||||
|
@ -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' },
|
||||
],
|
||||
};
|
@ -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(
|
||||
<div>
|
||||
<Provider store={store}>
|
||||
<PromSettings onOptionsChange={() => {}} options={options} />
|
||||
</div>
|
||||
</Provider>
|
||||
);
|
||||
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(
|
||||
<div>
|
||||
<Provider store={store}>
|
||||
<PromSettings onOptionsChange={() => {}} options={options} />
|
||||
</div>
|
||||
</Provider>
|
||||
);
|
||||
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(
|
||||
<div>
|
||||
<Provider store={store}>
|
||||
<PromSettings onOptionsChange={() => {}} options={options} />
|
||||
</div>
|
||||
</Provider>
|
||||
);
|
||||
expect(screen.getByText('GET')).toBeInTheDocument();
|
||||
});
|
||||
|
@ -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<DataSourcePluginOptionsEditorProps<PromOptions>, '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<PromOptions>,
|
||||
onOptionsChange: (options: DataSourceSettingsType<PromOptions>) => void,
|
||||
onUpdate: (dataSource: DataSourceSettingsType<PromOptions>) => Promise<DataSourceSettingsType<PromOptions>>
|
||||
) => {
|
||||
// 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 (
|
||||
<>
|
||||
<div className="gf-form-group">
|
||||
{/* Scrape interval */}
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<FormField
|
||||
@ -59,6 +164,7 @@ export const PromSettings = (props: Props) => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* Query Timeout */}
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<FormField
|
||||
@ -78,12 +184,13 @@ export const PromSettings = (props: Props) => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* HTTP Method */}
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel
|
||||
width={13}
|
||||
tooltip="You can use either POST or GET HTTP method to query your Prometheus data source. POST is the recommended 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."
|
||||
>
|
||||
HTTP Method
|
||||
HTTP method
|
||||
</InlineFormLabel>
|
||||
<Select
|
||||
aria-label="Select HTTP method"
|
||||
@ -94,6 +201,77 @@ export const PromSettings = (props: Props) => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className="page-heading">Type and version</h3>
|
||||
{!options.jsonData.prometheusType && !options.jsonData.prometheusVersion && options.readOnly && (
|
||||
<div style={{ marginBottom: '12px' }}>
|
||||
For more information on configuring prometheus type and version in data sources, see the{' '}
|
||||
<a
|
||||
style={{ textDecoration: 'underline' }}
|
||||
href="https://grafana.com/docs/grafana/latest/administration/provisioning/"
|
||||
>
|
||||
provisioning documentation
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
)}
|
||||
<div className="gf-form-group">
|
||||
<div className="gf-form">
|
||||
<div className="gf-form">
|
||||
<FormField
|
||||
label="Prometheus type"
|
||||
labelWidth={13}
|
||||
inputEl={
|
||||
<Select
|
||||
aria-label="Prometheus type"
|
||||
options={prometheusFlavorSelectItems}
|
||||
value={prometheusFlavorSelectItems.find((o) => o.value === options.jsonData.prometheusType)}
|
||||
onChange={onChangeHandler(
|
||||
'prometheusType',
|
||||
{
|
||||
...options,
|
||||
jsonData: { ...options.jsonData, prometheusVersion: undefined },
|
||||
},
|
||||
(options) => {
|
||||
// Check buildinfo api and set default version if we can
|
||||
setPrometheusVersion(options, onOptionsChange, onUpdate);
|
||||
return onOptionsChange({
|
||||
...options,
|
||||
jsonData: { ...options.jsonData, prometheusVersion: undefined },
|
||||
});
|
||||
}
|
||||
)}
|
||||
width={20}
|
||||
/>
|
||||
}
|
||||
tooltip="Set this to the type of your prometheus database, e.g. Prometheus, Cortex, Mimir or Thanos. Changing this field will save your current settings, and attempt to detect the version."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
{options.jsonData.prometheusType && (
|
||||
<div className="gf-form">
|
||||
<FormField
|
||||
label={`${options.jsonData.prometheusType} version`}
|
||||
labelWidth={13}
|
||||
inputEl={
|
||||
<Select
|
||||
aria-label={`${options.jsonData.prometheusType} type`}
|
||||
options={PromFlavorVersions[options.jsonData.prometheusType]}
|
||||
value={PromFlavorVersions[options.jsonData.prometheusType]?.find(
|
||||
(o) => o.value === options.jsonData.prometheusVersion
|
||||
)}
|
||||
onChange={onChangeHandler('prometheusVersion', options, onOptionsChange)}
|
||||
width={20}
|
||||
/>
|
||||
}
|
||||
tooltip={`Use this to set the version of your ${options.jsonData.prometheusType} instance if it is not automatically configured.`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className="page-heading">Misc</h3>
|
||||
<div className="gf-form-group">
|
||||
<div className="gf-form">
|
||||
@ -113,7 +291,7 @@ export const PromSettings = (props: Props) => {
|
||||
<FormField
|
||||
label="Custom query parameters"
|
||||
labelWidth={14}
|
||||
tooltip="Add Custom parameters to all Prometheus or Thanos queries."
|
||||
tooltip="Add custom parameters to all Prometheus or Thanos queries."
|
||||
inputEl={
|
||||
<Input
|
||||
className="width-25"
|
||||
|
@ -89,6 +89,8 @@ export class PrometheusDatasource
|
||||
exemplarTraceIdDestinations: ExemplarTraceIdDestination[] | undefined;
|
||||
lookupsDisabled: boolean;
|
||||
customQueryParameters: any;
|
||||
datasourceConfigurationPrometheusFlavor?: PromApplication;
|
||||
datasourceConfigurationPrometheusVersion?: string;
|
||||
exemplarsAvailable: boolean;
|
||||
subType: PromApplication;
|
||||
rulerEnabled: boolean;
|
||||
@ -121,6 +123,8 @@ export class PrometheusDatasource
|
||||
this.languageProvider = languageProvider ?? new PrometheusLanguageProvider(this);
|
||||
this.lookupsDisabled = instanceSettings.jsonData.disableMetricsLookup ?? false;
|
||||
this.customQueryParameters = new URLSearchParams(instanceSettings.jsonData.customQueryParameters);
|
||||
this.datasourceConfigurationPrometheusFlavor = instanceSettings.jsonData.prometheusType;
|
||||
this.datasourceConfigurationPrometheusVersion = instanceSettings.jsonData.prometheusVersion;
|
||||
this.variables = new PrometheusVariableSupport(this, this.templateSrv, this.timeSrv);
|
||||
this.exemplarsAvailable = true;
|
||||
|
||||
@ -849,11 +853,6 @@ export class PrometheusDatasource
|
||||
);
|
||||
}
|
||||
|
||||
async getSubtitle(): Promise<JSX.Element | null> {
|
||||
const buildInfo = await this.getBuildInfo();
|
||||
return buildInfo ? this.getBuildInfoMessage(buildInfo) : null;
|
||||
}
|
||||
|
||||
async getTagKeys(options?: any) {
|
||||
if (options?.series) {
|
||||
// Get tags for the provided series only
|
||||
@ -902,23 +901,28 @@ export class PrometheusDatasource
|
||||
);
|
||||
|
||||
const LOGOS = {
|
||||
[PromApplication.Lotex]: '/public/app/plugins/datasource/prometheus/img/cortex_logo.svg',
|
||||
[PromApplication.Cortex]: '/public/app/plugins/datasource/prometheus/img/cortex_logo.svg',
|
||||
[PromApplication.Mimir]: '/public/app/plugins/datasource/prometheus/img/mimir_logo.svg',
|
||||
[PromApplication.Prometheus]: '/public/app/plugins/datasource/prometheus/img/prometheus_logo.svg',
|
||||
[PromApplication.Thanos]: '/public/app/plugins/datasource/prometheus/img/thanos_logo.svg',
|
||||
};
|
||||
|
||||
const COLORS: Record<PromApplication, BadgeColor> = {
|
||||
[PromApplication.Lotex]: 'blue',
|
||||
[PromApplication.Cortex]: 'blue',
|
||||
[PromApplication.Mimir]: 'orange',
|
||||
[PromApplication.Prometheus]: 'red',
|
||||
[PromApplication.Thanos]: 'purple', // Purple hex taken from thanos.io
|
||||
};
|
||||
|
||||
const AppDisplayNames: Record<PromApplication, string> = {
|
||||
[PromApplication.Lotex]: 'Cortex',
|
||||
[PromApplication.Cortex]: 'Cortex',
|
||||
[PromApplication.Mimir]: 'Mimir',
|
||||
[PromApplication.Prometheus]: 'Prometheus',
|
||||
[PromApplication.Thanos]: 'Thanos',
|
||||
};
|
||||
|
||||
const application = this.datasourceConfigurationPrometheusFlavor ?? buildInfo.application;
|
||||
|
||||
// this will inform the user about what "subtype" the datasource is; Mimir, Cortex or vanilla Prometheus
|
||||
const applicationSubType = (
|
||||
<Badge
|
||||
@ -926,13 +930,13 @@ export class PrometheusDatasource
|
||||
<span>
|
||||
<img
|
||||
style={{ width: 14, height: 14, verticalAlign: 'text-bottom' }}
|
||||
src={LOGOS[buildInfo.application ?? PromApplication.Prometheus]}
|
||||
src={LOGOS[application ?? PromApplication.Prometheus]}
|
||||
alt=""
|
||||
/>{' '}
|
||||
{buildInfo.application ? AppDisplayNames[buildInfo.application] : 'Unknown'}
|
||||
{application ? AppDisplayNames[application] : 'Unknown'}
|
||||
</span>
|
||||
}
|
||||
color={COLORS[buildInfo.application ?? PromApplication.Prometheus]}
|
||||
color={COLORS[application ?? PromApplication.Prometheus]}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -0,0 +1 @@
|
||||
<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>
|
After Width: | Height: | Size: 958 B |
@ -1,5 +1,7 @@
|
||||
import { DataQuery, DataSourceJsonData, QueryResultMeta, ScopedVars } from '@grafana/data';
|
||||
|
||||
import { PromApplication } from '../../../types/unified-alerting-dto';
|
||||
|
||||
import { QueryEditorMode } from './querybuilder/shared/types';
|
||||
|
||||
export interface PromQuery extends DataQuery {
|
||||
@ -30,6 +32,8 @@ export interface PromOptions extends DataSourceJsonData {
|
||||
customQueryParameters?: string;
|
||||
disableMetricsLookup?: boolean;
|
||||
exemplarTraceIdDestinations?: ExemplarTraceIdDestination[];
|
||||
prometheusType?: PromApplication;
|
||||
prometheusVersion?: string;
|
||||
}
|
||||
|
||||
export enum PromQueryType {
|
||||
|
@ -54,10 +54,12 @@ export enum PromRuleType {
|
||||
Alerting = 'alerting',
|
||||
Recording = 'recording',
|
||||
}
|
||||
|
||||
export enum PromApplication {
|
||||
Lotex = 'Lotex',
|
||||
Cortex = 'Cortex',
|
||||
Mimir = 'Mimir',
|
||||
Prometheus = 'Prometheus',
|
||||
Thanos = 'Thanos',
|
||||
}
|
||||
|
||||
export interface PromBuildInfoResponse {
|
||||
|
Loading…
Reference in New Issue
Block a user