diff --git a/.betterer.results b/.betterer.results index e7f154cafba..5e3d035af55 100644 --- a/.betterer.results +++ b/.betterer.results @@ -4753,7 +4753,7 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "19"], [0, 0, 0, "Unexpected any. Specify a different type.", "20"], [0, 0, 0, "Unexpected any. Specify a different type.", "21"], - [0, 0, 0, "Do not use any type assertions.", "22"], + [0, 0, 0, "Unexpected any. Specify a different type.", "22"], [0, 0, 0, "Unexpected any. Specify a different type.", "23"], [0, 0, 0, "Unexpected any. Specify a different type.", "24"], [0, 0, 0, "Unexpected any. Specify a different type.", "25"], @@ -4762,9 +4762,7 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "28"], [0, 0, 0, "Unexpected any. Specify a different type.", "29"], [0, 0, 0, "Unexpected any. Specify a different type.", "30"], - [0, 0, 0, "Unexpected any. Specify a different type.", "31"], - [0, 0, 0, "Unexpected any. Specify a different type.", "32"], - [0, 0, 0, "Unexpected any. Specify a different type.", "33"] + [0, 0, 0, "Unexpected any. Specify a different type.", "31"] ], "public/app/plugins/datasource/prometheus/language_provider.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], diff --git a/e2e/various-suite/exemplars.spec.ts b/e2e/various-suite/exemplars.spec.ts index 7b6f5ad6e1a..d341878b4cc 100644 --- a/e2e/various-suite/exemplars.spec.ts +++ b/e2e/various-suite/exemplars.spec.ts @@ -4,7 +4,7 @@ const dataSourceName = 'PromExemplar'; const addDataSource = () => { e2e.flows.addDataSource({ type: 'Prometheus', - expectedAlertMessage: 'Error reading Prometheus', + expectedAlertMessage: 'saved', name: dataSourceName, form: () => { e2e.components.DataSource.Prometheus.configPage.exemplarsAddButton().click(); diff --git a/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx b/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx index d971c03a813..cf4a0a26981 100644 --- a/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx +++ b/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx @@ -73,6 +73,7 @@ export const DataSourceHttpSettings = (props: HttpSettingsProps) => { azureAuthSettings, renderSigV4Editor, secureSocksDSProxyEnabled, + connectionElements, } = props; let urlTooltip; const [isAccessHelpVisible, setIsAccessHelpVisible] = useState(false); @@ -93,6 +94,7 @@ export const DataSourceHttpSettings = (props: HttpSettingsProps) => { urlTooltip = ( <> Your access method is Browser, this means the URL needs to be accessible from the browser. + {connectionElements?.tooltip} ); break; @@ -101,11 +103,12 @@ export const DataSourceHttpSettings = (props: HttpSettingsProps) => { <> Your access method is Server, this means the URL needs to be accessible from the grafana backend/server. + {connectionElements?.tooltip} ); break; default: - urlTooltip = 'Specify a complete HTTP URL (for example http://your_server:8080)'; + urlTooltip = <>Specify a complete HTTP URL (for example http://your_server:8080) {connectionElements?.tooltip}; } const accessSelect = ( @@ -143,14 +146,24 @@ export const DataSourceHttpSettings = (props: HttpSettingsProps) => { const azureAuthEnabled: boolean = (azureAuthSettings?.azureAuthSupported && azureAuthSettings.getAzureAuthEnabled(dataSourceConfig)) || false; + const connectionLabel = connectionElements?.label ? connectionElements?.label : 'URL'; + return (
<>

HTTP

-
- -
+ {defaultUrl && ( +
+ +
+ )} {showAccessOptions && ( <> diff --git a/packages/grafana-ui/src/components/DataSourceSettings/types.ts b/packages/grafana-ui/src/components/DataSourceSettings/types.ts index 2608fb6cefa..81d20df8f2a 100644 --- a/packages/grafana-ui/src/components/DataSourceSettings/types.ts +++ b/packages/grafana-ui/src/components/DataSourceSettings/types.ts @@ -30,7 +30,7 @@ export interface HttpSettingsBaseProps + extends Pick, 'options' | 'onOptionsChange'> {} + +export interface AlertingConfig extends DataSourceJsonData { + manageAlerts?: boolean; +} + +export function AlertingSettingsOverhaul({ + options, + onOptionsChange, +}: Props): JSX.Element { + const theme = useTheme2(); + const styles = overhaulStyles(theme); + + return ( + <> +
Alerting
+
+
+
+ + Manage alert rules for this data source. To manage other alerting resources, add an Alertmanager data + source. {docsTip()} + + } + interactive={true} + className={styles.switchField} + > + + onOptionsChange({ + ...options, + jsonData: { ...options.jsonData, manageAlerts: event!.currentTarget.checked }, + }) + } + /> + +
+
+
+ + ); +} diff --git a/public/app/plugins/datasource/prometheus/configuration/AzureAuthSettings.tsx b/public/app/plugins/datasource/prometheus/configuration/AzureAuthSettings.tsx index b7746369dab..9d5d90ec865 100644 --- a/public/app/plugins/datasource/prometheus/configuration/AzureAuthSettings.tsx +++ b/public/app/plugins/datasource/prometheus/configuration/AzureAuthSettings.tsx @@ -45,7 +45,7 @@ export const AzureAuthSettings = (props: HttpSettingsBaseProps) => { return ( <> -
Azure Authentication
+
Azure authentication
{ /> {overrideAudienceAllowed && ( <> -
Azure Configuration
+
Azure configuration
diff --git a/public/app/plugins/datasource/prometheus/configuration/ConfigEditor.test.tsx b/public/app/plugins/datasource/prometheus/configuration/ConfigEditor.test.tsx new file mode 100644 index 00000000000..9ce9e66c563 --- /dev/null +++ b/public/app/plugins/datasource/prometheus/configuration/ConfigEditor.test.tsx @@ -0,0 +1,93 @@ +import React from 'react'; + +import { FieldValidationMessage } from '@grafana/ui'; + +import { validateInput } from './ConfigEditor'; +import { DURATION_REGEX, MULTIPLE_DURATION_REGEX } from './PromSettings'; + +const VALID_URL_REGEX = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; + +const error = Value is not valid; +// replaces promSettingsValidationEvents to display a onBlur for duration input errors +describe('promSettings validateInput', () => { + it.each` + value | expected + ${'1ms'} | ${true} + ${'1M'} | ${true} + ${'1w'} | ${true} + ${'1d'} | ${true} + ${'1h'} | ${true} + ${'1m'} | ${true} + ${'1s'} | ${true} + ${'1y'} | ${true} + `( + "Single duration regex, when calling the rule with correct formatted value: '$value' then result should be '$expected'", + ({ value, expected }) => { + expect(validateInput(value, DURATION_REGEX)).toBe(expected); + } + ); + + it.each` + value | expected + ${'1M 2s'} | ${true} + ${'1w 2d'} | ${true} + ${'1d 2m'} | ${true} + ${'1h 2m'} | ${true} + ${'1m 2s'} | ${true} + `( + "Multiple duration regex, when calling the rule with correct formatted value: '$value' then result should be '$expected'", + ({ value, expected }) => { + expect(validateInput(value, MULTIPLE_DURATION_REGEX)).toBe(expected); + } + ); + + it.each` + value | expected + ${'1 ms'} | ${error} + ${'1x'} | ${error} + ${' '} | ${error} + ${'w'} | ${error} + ${'1.0s'} | ${error} + `( + "when calling the rule with incorrect formatted value: '$value' then result should be '$expected'", + ({ value, expected }) => { + expect(validateInput(value, DURATION_REGEX)).toStrictEqual(expected); + } + ); + + it.each` + value | expected + ${'frp://'} | ${error} + ${'htp://'} | ${error} + ${'httpss:??'} | ${error} + ${'http@//'} | ${error} + ${'http:||'} | ${error} + ${'http://'} | ${error} + ${'https://'} | ${error} + ${'ftp://'} | ${error} + `( + "Url incorrect formatting, when calling the rule with correct formatted value: '$value' then result should be '$expected'", + ({ value, expected }) => { + expect(validateInput(value, VALID_URL_REGEX)).toStrictEqual(expected); + } + ); + + it.each` + value | expected + ${'ftp://example'} | ${true} + ${'http://example'} | ${true} + ${'https://example'} | ${true} + `( + "Url correct formatting, when calling the rule with correct formatted value: '$value' then result should be '$expected'", + ({ value, expected }) => { + expect(validateInput(value, VALID_URL_REGEX)).toBe(expected); + } + ); + + it('should display a custom validation message', () => { + const invalidDuration = 'invalid'; + const customMessage = 'This is invalid'; + const errorWithCustomMessage = {customMessage}; + expect(validateInput(invalidDuration, DURATION_REGEX, customMessage)).toStrictEqual(errorWithCustomMessage); + }); +}); diff --git a/public/app/plugins/datasource/prometheus/configuration/ConfigEditor.tsx b/public/app/plugins/datasource/prometheus/configuration/ConfigEditor.tsx index 6470645ed3d..7df57461f82 100644 --- a/public/app/plugins/datasource/prometheus/configuration/ConfigEditor.tsx +++ b/public/app/plugins/datasource/prometheus/configuration/ConfigEditor.tsx @@ -1,19 +1,24 @@ +import { css } from '@emotion/css'; import React, { useRef } from 'react'; import { SIGV4ConnectionConfig } from '@grafana/aws-sdk'; -import { DataSourcePluginOptionsEditorProps, DataSourceSettings } from '@grafana/data'; -import { AlertingSettings, DataSourceHttpSettings, Alert } from '@grafana/ui'; +import { DataSourcePluginOptionsEditorProps, DataSourceSettings, GrafanaTheme2 } from '@grafana/data'; +import { Alert, DataSourceHttpSettings, FieldValidationMessage, useTheme2 } from '@grafana/ui'; import { config } from 'app/core/config'; import { PromOptions } from '../types'; +import { AlertingSettingsOverhaul } from './AlertingSettingsOverhaul'; import { AzureAuthSettings } from './AzureAuthSettings'; import { hasCredentials, setDefaultCredentials, resetCredentials } from './AzureCredentialsConfig'; import { PromSettings } from './PromSettings'; +export const PROM_CONFIG_LABEL_WIDTH = 30; + export type Props = DataSourcePluginOptionsEditorProps; export const ConfigEditor = (props: Props) => { const { options, onOptionsChange } = props; + // use ref so this is evaluated only first time it renders and the select does not disappear suddenly. const showAccessOptions = useRef(props.options.access === 'direct'); @@ -25,14 +30,16 @@ export const ConfigEditor = (props: Props) => { azureSettingsUI: AzureAuthSettings, }; + const theme = useTheme2(); + const styles = overhaulStyles(theme); + return ( <> {options.access === 'direct' && ( - Browser access mode in the Prometheus datasource is no longer available. Switch to server access mode. + Browser access mode in the Prometheus data source is no longer available. Switch to server access mode. )} - { azureAuthSettings={azureAuthSettings} renderSigV4Editor={} secureSocksDSProxyEnabled={config.secureSocksDSProxyEnabled} + connectionElements={{ + label: 'Prometheus server URL', + tooltip: docsTip(), + }} /> + <> +
+

Additional settings

+

+ Additional settings are optional settings that can be configured for more control over your data source. +

- options={options} onOptionsChange={onOptionsChange} /> + options={options} onOptionsChange={onOptionsChange} /> - + + ); }; +/** + * Use this to return a url in a tooltip in a field. Don't forget to make the field interactive to be able to click on the tooltip + * @param url + * @returns + */ +export function docsTip(url?: string) { + const docsUrl = 'https://grafana.com/docs/grafana/latest/datasources/prometheus/#configure-the-data-source'; + + return ( + + Visit docs for more details here. + + ); +} + +export const validateInput = ( + input: string, + pattern: string | RegExp, + errorMessage?: string +): boolean | JSX.Element => { + const defaultErrorMessage = 'Value is not valid'; + if (input && !input.match(pattern)) { + return {errorMessage ? errorMessage : defaultErrorMessage}; + } else { + return true; + } +}; + +export function overhaulStyles(theme: GrafanaTheme2) { + return { + additionalSettings: css` + margin-bottom: 25px; + `, + secondaryGrey: css` + color: ${theme.colors.secondary.text}; + opacity: 65%; + `, + inlineError: css` + margin: 0px 0px 4px 245px; + `, + switchField: css` + align-items: center; + `, + sectionHeaderPadding: css` + padding-top: 32px; + `, + sectionBottomPadding: css` + padding-bottom: 28px; + `, + subsectionText: css` + font-size: 12px; + `, + hrBottomSpace: css` + margin-bottom: 56px; + `, + hrTopSpace: css` + margin-top: 50px; + `, + textUnderline: css` + text-decoration: underline; + `, + versionMargin: css` + margin-bottom: 12px; + `, + }; +} diff --git a/public/app/plugins/datasource/prometheus/configuration/ExemplarSetting.tsx b/public/app/plugins/datasource/prometheus/configuration/ExemplarSetting.tsx index acc75e8e29b..ba13f98e692 100644 --- a/public/app/plugins/datasource/prometheus/configuration/ExemplarSetting.tsx +++ b/public/app/plugins/datasource/prometheus/configuration/ExemplarSetting.tsx @@ -1,12 +1,13 @@ -import { css } from '@emotion/css'; import React, { useState } from 'react'; import { selectors } from '@grafana/e2e-selectors'; import { DataSourcePicker } from '@grafana/runtime'; -import { Button, InlineField, InlineSwitch, Input } from '@grafana/ui'; +import { Button, InlineField, Input, Switch, useTheme2 } from '@grafana/ui'; import { ExemplarTraceIdDestination } from '../types'; +import { docsTip, overhaulStyles, PROM_CONFIG_LABEL_WIDTH } from './ConfigEditor'; + type Props = { value: ExemplarTraceIdDestination; onChange: (value: ExemplarTraceIdDestination) => void; @@ -17,38 +18,40 @@ type Props = { export default function ExemplarSetting({ value, onChange, onDelete, disabled }: Props) { const [isInternalLink, setIsInternalLink] = useState(Boolean(value.datasourceUid)); + const theme = useTheme2(); + const styles = overhaulStyles(theme); + return (
- + + Enable this option if you have an internal link. When enabled, this reveals the data source selector. Select + the backend tracing data store for your exemplar data. {docsTip()} + + } + interactive={true} + className={styles.switchField} + > <> - setIsInternalLink(ev.currentTarget.checked)} /> - {!disabled && ( -
); } diff --git a/public/app/plugins/datasource/prometheus/configuration/ExemplarsSettings.tsx b/public/app/plugins/datasource/prometheus/configuration/ExemplarsSettings.tsx index 7c8abc3ae80..b13f7105c18 100644 --- a/public/app/plugins/datasource/prometheus/configuration/ExemplarsSettings.tsx +++ b/public/app/plugins/datasource/prometheus/configuration/ExemplarsSettings.tsx @@ -2,10 +2,11 @@ import { css } from '@emotion/css'; import React from 'react'; import { selectors } from '@grafana/e2e-selectors'; -import { Button } from '@grafana/ui'; +import { Button, useTheme2 } from '@grafana/ui'; import { ExemplarTraceIdDestination } from '../types'; +import { overhaulStyles } from './ConfigEditor'; import ExemplarSetting from './ExemplarSetting'; type Props = { @@ -15,9 +16,11 @@ type Props = { }; export function ExemplarsSettings({ options, onChange, disabled }: Props) { + const theme = useTheme2(); + const styles = overhaulStyles(theme); return ( - <> -

Exemplars

+
+
Exemplars
{options && options.map((option, index) => { @@ -57,8 +60,7 @@ export function ExemplarsSettings({ options, onChange, disabled }: Props) { Add )} - {disabled && !options && No exemplars configurations} - +
); } diff --git a/public/app/plugins/datasource/prometheus/configuration/PromSettings.test.tsx b/public/app/plugins/datasource/prometheus/configuration/PromSettings.test.tsx index bf91f3d5be8..aea4755989e 100644 --- a/public/app/plugins/datasource/prometheus/configuration/PromSettings.test.tsx +++ b/public/app/plugins/datasource/prometheus/configuration/PromSettings.test.tsx @@ -3,11 +3,10 @@ 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 { getValueFromEventItem, PromSettings } from './PromSettings'; import { createDefaultConfigOptions } from './mocks'; describe('PromSettings', () => { @@ -38,58 +37,6 @@ describe('PromSettings', () => { }); }); - describe('promSettingsValidationEvents', () => { - const validationEvents = promSettingsValidationEvents; - - it('should have one event handlers', () => { - expect(Object.keys(validationEvents).length).toEqual(1); - }); - - it('should have an onBlur handler', () => { - expect(validationEvents.hasOwnProperty(EventsWithValidation.onBlur)).toBe(true); - }); - - it('should have one rule', () => { - expect(validationEvents[EventsWithValidation.onBlur].length).toEqual(1); - }); - - describe('when calling the rule with an empty string', () => { - it('then it should return true', () => { - expect(validationEvents[EventsWithValidation.onBlur][0].rule('')).toBe(true); - }); - }); - - it.each` - value | expected - ${'1ms'} | ${true} - ${'1M'} | ${true} - ${'1w'} | ${true} - ${'1d'} | ${true} - ${'1h'} | ${true} - ${'1m'} | ${true} - ${'1s'} | ${true} - ${'1y'} | ${true} - `( - "when calling the rule with correct formatted value: '$value' then result should be '$expected'", - ({ value, expected }) => { - expect(validationEvents[EventsWithValidation.onBlur][0].rule(value)).toBe(expected); - } - ); - - it.each` - value | expected - ${'1 ms'} | ${false} - ${'1x'} | ${false} - ${' '} | ${false} - ${'w'} | ${false} - ${'1.0s'} | ${false} - `( - "when calling the rule with incorrect formatted value: '$value' then result should be '$expected'", - ({ value, expected }) => { - expect(validationEvents[EventsWithValidation.onBlur][0].rule(value)).toBe(expected); - } - ); - }); describe('PromSettings component', () => { const defaultProps = createDefaultConfigOptions(); diff --git a/public/app/plugins/datasource/prometheus/configuration/PromSettings.tsx b/public/app/plugins/datasource/prometheus/configuration/PromSettings.tsx index eeabf4a8591..07fb205f58f 100644 --- a/public/app/plugins/datasource/prometheus/configuration/PromSettings.tsx +++ b/public/app/plugins/datasource/prometheus/configuration/PromSettings.tsx @@ -1,24 +1,15 @@ -import React, { SyntheticEvent } from 'react'; +import React, { SyntheticEvent, useState } from 'react'; import semver from 'semver/preload'; import { DataSourcePluginOptionsEditorProps, DataSourceSettings as DataSourceSettingsType, - isValidDuration, onUpdateDatasourceJsonDataOptionChecked, SelectableValue, updateDatasourcePluginJsonDataOption, } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime/src'; -import { - EventsWithValidation, - InlineField, - InlineFormLabel, - InlineSwitch, - LegacyForms, - regexValidation, - Select, -} from '@grafana/ui'; +import { InlineField, Input, Select, Switch, useTheme2 } from '@grafana/ui'; import config from '../../../../core/config'; import { useUpdateDatasource } from '../../../../features/datasources/state'; @@ -27,11 +18,10 @@ import { QueryEditorMode } from '../querybuilder/shared/types'; import { defaultPrometheusQueryOverlapWindow } from '../querycache/QueryCache'; import { PrometheusCacheLevel, PromOptions } from '../types'; +import { docsTip, overhaulStyles, PROM_CONFIG_LABEL_WIDTH, validateInput } from './ConfigEditor'; import { ExemplarsSettings } from './ExemplarsSettings'; import { PromFlavorVersions } from './PromFlavorVersions'; -const { Input, FormField } = LegacyForms; - const httpOptions = [ { value: 'POST', label: 'POST' }, { value: 'GET', label: 'GET' }, @@ -60,6 +50,13 @@ const prometheusFlavorSelectItems: PrometheusSelectItemsType = [ type Props = Pick, 'options' | 'onOptionsChange'>; +// single duration input +export const DURATION_REGEX = /^$|^\d+(ms|[Mwdhmsy])$/; + +// multiple duration input +export const MULTIPLE_DURATION_REGEX = /(\d+)(.+)/; + +const durationError = 'Value is not valid, you can use number with time unit specifier: y, M, w, d, h, m, s'; /** * 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 @@ -158,76 +155,132 @@ export const PromSettings = (props: Props) => { options.jsonData.httpMethod = 'POST'; } + const theme = useTheme2(); + const styles = overhaulStyles(theme); + + type ValidDuration = { + timeInterval: string; + queryTimeout: string; + incrementalQueryOverlapWindow: string; + }; + + const [validDuration, updateValidDuration] = useState({ + timeInterval: '', + queryTimeout: '', + incrementalQueryOverlapWindow: '', + }); + return ( <> +
Interval behaviour
{/* Scrape interval */}
- + This interval is how frequently Prometheus scrapes targets. Set this to the typical scrape and + evaluation interval configured in your Prometheus config file. If you set this to a greater value than + your Prometheus config file interval, Grafana will evaluate the data according to this interval and + you will see less data points. Defaults to 15s. {docsTip()} + + } + interactive={true} + disabled={options.readOnly} + > + <> updateValidDuration({ ...validDuration, timeInterval: e.currentTarget.value })} /> - } - tooltip="Set this to the typical scrape and evaluation interval configured in Prometheus. Defaults to 15s." - /> + {validateInput(validDuration.timeInterval, DURATION_REGEX, durationError)} + +
{/* Query Timeout */}
- Set the Prometheus query timeout. {docsTip()}} + interactive={true} + disabled={options.readOnly} + > + <> updateValidDuration({ ...validDuration, queryTimeout: e.currentTarget.value })} /> - } - tooltip="Set the Prometheus query timeout." - /> + {validateInput(validDuration.queryTimeout, DURATION_REGEX, durationError)} + +
- {/* HTTP Method */} -
- - HTTP method - - o.value === options.jsonData.defaultEditor) ?? + editorOptions.find((o) => o.value === QueryEditorMode.Builder) + } + onChange={onChangeHandler('defaultEditor', options, onOptionsChange)} + width={40} + /> + +
+
+ + 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. {docsTip()} + + } + interactive={true} + disabled={options.readOnly} + className={styles.switchField} + > + + +
+
+ +
Performance
{!options.jsonData.prometheusType && !options.jsonData.prometheusVersion && options.readOnly && ( -
+
For more information on configuring prometheus type and version in data sources, see the{' '} provisioning documentation @@ -236,182 +289,212 @@ export const PromSettings = (props: Props) => {
)}
-
+
- o.value === options.jsonData.prometheusType)} - onChange={onChangeHandler( - 'prometheusType', - { + labelWidth={PROM_CONFIG_LABEL_WIDTH} + 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. Certain types of + Prometheus support or do not support various APIs. For example, some types support regex matching for + label queries to improve performance. Some types have an API for metadata. If you set this incorrectly + you may experience odd behavior when querying metrics and labels. Please check your Prometheus + documentation to ensure you enter the correct type. {docsTip()} + + } + interactive={true} + disabled={options.readOnly} + > + o.value === options.jsonData.prometheusVersion + )} + onChange={onChangeHandler('prometheusVersion', options, onOptionsChange)} + width={40} /> - } - /> -
+ +
+ )}
{config.featureToggles.prometheusResourceBrowserCache && (
- o.value === options.jsonData.cacheLevel) ?? PrometheusCacheLevel.Low - } - /> + labelWidth={PROM_CONFIG_LABEL_WIDTH} + tooltip={ + <> + Sets the browser caching level for editor queries. Higher cache settings are recommended for high + cardinality data sources. + } - /> + interactive={true} + disabled={options.readOnly} + > + isValidDuration(value), - errorMessage: 'Invalid duration. Example values: 100s, 10m', - }, - ], - }} + onBlur={(e) => + updateValidDuration({ ...validDuration, incrementalQueryOverlapWindow: e.currentTarget.value }) + } className="width-25" value={options.jsonData.incrementalQueryOverlapWindow ?? defaultPrometheusQueryOverlapWindow} onChange={onChangeHandler('incrementalQueryOverlapWindow', options, onOptionsChange)} spellCheck={false} - disabled={options.readOnly} /> - } - /> + {validateInput(validDuration.incrementalQueryOverlapWindow, MULTIPLE_DURATION_REGEX, durationError)} + + )}
+ +
Other
+
+
+
+ + 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 ‘&’. {docsTip()} + + } + interactive={true} + disabled={options.readOnly} + > + + +
+
+
+ {/* HTTP Method */} +
+ + 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. {docsTip()} + + } + interactive={true} + label="HTTP method" + disabled={options.readOnly} + > +