Prometheus: Refactoring in datasource.ts (#107208)

* start cleanup

* update vars

* prettier
pull/107300/head
Gareth 3 weeks ago committed by GitHub
parent b2aaa9b3ce
commit 643dee6739
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 29
      packages/grafana-prometheus/src/constants.ts
  2. 185
      packages/grafana-prometheus/src/datasource.ts
  3. 4
      packages/grafana-prometheus/src/index.ts

@ -6,28 +6,43 @@ export const PROMETHEUS_QUERY_BUILDER_MAX_RESULTS = 1000;
export const PROM_CONFIG_LABEL_WIDTH = 30;
export const LIST_ITEM_SIZE = 25;
export const LAST_USED_LABELS_KEY = 'grafana.datasources.prometheus.browser.labels';
// single duration input
export const DURATION_REGEX = /^$|^\d+(ms|[Mwdhmsy])$/;
// multiple duration input
export const MULTIPLE_DURATION_REGEX = /(\d+)(.+)/;
export const NON_NEGATIVE_INTEGER_REGEX = /^(0|[1-9]\d*)(\.\d+)?(e\+?\d+)?$/; // non-negative integers, including scientific notation
export const EMPTY_SELECTOR = '{}';
export const DEFAULT_SERIES_LIMIT = 40000;
export const MATCH_ALL_LABELS_STR = '__name__!=""';
export const MATCH_ALL_LABELS = '{__name__!=""}';
export const METRIC_LABEL = '__name__';
/**
* @deprecated
*/
export const REMOVE_SERIES_LIMIT = 'none';
export const METRIC_LABEL = '__name__';
export const durationError = 'Value is not valid, you can use number with time unit specifier: y, M, w, d, h, m, s';
export const countError = 'Value is not valid, you can use non-negative integers, including scientific notation';
export const seriesLimitError =
'Value is not valid, you can use only numbers or leave it empty to use default limit or set 0 to have no limit.';
export const InstantQueryRefIdIndex = '-Instant';
export const GET_AND_POST_METADATA_ENDPOINTS = [
'api/v1/query',
'api/v1/query_range',
'api/v1/series',
'api/v1/labels',
'suggestions',
];
/**
* @deprecated
*/
export const REMOVE_SERIES_LIMIT = 'none';

@ -1,4 +1,3 @@
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/datasource.ts
import { defaults } from 'lodash';
import { tz } from 'moment-timezone';
import { lastValueFrom, Observable, throwError } from 'rxjs';
@ -42,7 +41,12 @@ import {
import { addLabelToQuery } from './add_label_to_query';
import { PrometheusAnnotationSupport } from './annotations';
import { DEFAULT_SERIES_LIMIT, SUGGESTIONS_LIMIT } from './constants';
import {
DEFAULT_SERIES_LIMIT,
GET_AND_POST_METADATA_ENDPOINTS,
InstantQueryRefIdIndex,
SUGGESTIONS_LIMIT,
} from './constants';
import { prometheusRegularEscape, prometheusSpecialRegexEscape } from './escaping';
import {
exportToAbstractQuery,
@ -72,44 +76,34 @@ import {
import { utf8Support, wrapUtf8Filters } from './utf8_support';
import { PrometheusVariableSupport } from './variables';
const GET_AND_POST_METADATA_ENDPOINTS = [
'api/v1/query',
'api/v1/query_range',
'api/v1/series',
'api/v1/labels',
'suggestions',
];
export const InstantQueryRefIdIndex = '-Instant';
export class PrometheusDatasource
extends DataSourceWithBackend<PromQuery, PromOptions>
implements DataSourceWithQueryImportSupport<PromQuery>, DataSourceWithQueryExportSupport<PromQuery>
{
type: string;
ruleMappings: RuleQueryMapping;
hasIncrementalQuery: boolean;
url: string;
id: number;
access: 'direct' | 'proxy';
basicAuth: any;
withCredentials: boolean;
interval: string;
httpMethod: string;
languageProvider: PrometheusLanguageProviderInterface;
exemplarTraceIdDestinations: ExemplarTraceIdDestination[] | undefined;
lookupsDisabled: boolean;
cache: QueryCache<PromQuery>;
cacheLevel: PrometheusCacheLevel;
customQueryParameters: URLSearchParams;
datasourceConfigurationPrometheusFlavor?: PromApplication;
datasourceConfigurationPrometheusVersion?: string;
disableRecordingRules: boolean;
defaultEditor?: QueryEditorMode;
exemplarTraceIdDestinations: ExemplarTraceIdDestination[] | undefined;
exemplarsAvailable: boolean;
cacheLevel: PrometheusCacheLevel;
cache: QueryCache<PromQuery>;
hasIncrementalQuery: boolean;
httpMethod: string;
id: number;
interval: string;
languageProvider: PrometheusLanguageProviderInterface;
lookupsDisabled: boolean;
metricNamesAutocompleteSuggestionLimit: number;
ruleMappings: RuleQueryMapping;
seriesEndpoint: boolean;
seriesLimit: number;
type: string;
url: string;
withCredentials: boolean;
defaultEditor?: QueryEditorMode;
constructor(
instanceSettings: DataSourceInstanceSettings<PromOptions>,
@ -118,48 +112,106 @@ export class PrometheusDatasource
) {
super(instanceSettings);
this.type = 'prometheus';
this.id = instanceSettings.id;
this.url = instanceSettings.url!;
// DATASOURCE CONFIGURATION PROPERTIES
this.access = instanceSettings.access;
this.basicAuth = instanceSettings.basicAuth;
this.withCredentials = Boolean(instanceSettings.withCredentials);
this.interval = instanceSettings.jsonData.timeInterval || '15s';
this.httpMethod = instanceSettings.jsonData.httpMethod || 'GET';
this.exemplarTraceIdDestinations = instanceSettings.jsonData.exemplarTraceIdDestinations;
this.hasIncrementalQuery = instanceSettings.jsonData.incrementalQuerying ?? false;
this.ruleMappings = {};
this.lookupsDisabled = instanceSettings.jsonData.disableMetricsLookup ?? false;
this.cache = new QueryCache({
getTargetSignature: this.getPrometheusTargetSignature.bind(this),
overlapString: instanceSettings.jsonData.incrementalQueryOverlapWindow ?? defaultPrometheusQueryOverlapWindow,
applyInterpolation: this.interpolateString.bind(this),
});
this.cacheLevel = instanceSettings.jsonData.cacheLevel ?? PrometheusCacheLevel.Low;
this.customQueryParameters = new URLSearchParams(instanceSettings.jsonData.customQueryParameters);
this.datasourceConfigurationPrometheusFlavor = instanceSettings.jsonData.prometheusType;
this.datasourceConfigurationPrometheusVersion = instanceSettings.jsonData.prometheusVersion;
this.seriesLimit = instanceSettings.jsonData.seriesLimit ?? DEFAULT_SERIES_LIMIT;
this.seriesEndpoint = instanceSettings.jsonData.seriesEndpoint ?? false;
this.defaultEditor = instanceSettings.jsonData.defaultEditor;
this.disableRecordingRules = instanceSettings.jsonData.disableRecordingRules ?? false;
this.variables = new PrometheusVariableSupport(this, this.templateSrv);
this.exemplarTraceIdDestinations = instanceSettings.jsonData.exemplarTraceIdDestinations;
this.exemplarsAvailable = true;
this.cacheLevel = instanceSettings.jsonData.cacheLevel ?? PrometheusCacheLevel.Low;
this.hasIncrementalQuery = instanceSettings.jsonData.incrementalQuerying ?? false;
this.httpMethod = instanceSettings.jsonData.httpMethod || 'GET';
this.id = instanceSettings.id;
this.interval = instanceSettings.jsonData.timeInterval || '15s';
this.lookupsDisabled = instanceSettings.jsonData.disableMetricsLookup ?? false;
this.metricNamesAutocompleteSuggestionLimit =
instanceSettings.jsonData.codeModeMetricNamesSuggestionLimit ?? SUGGESTIONS_LIMIT;
this.ruleMappings = {};
this.seriesEndpoint = instanceSettings.jsonData.seriesEndpoint ?? false;
this.seriesLimit = instanceSettings.jsonData.seriesLimit ?? DEFAULT_SERIES_LIMIT;
this.type = 'prometheus';
this.url = instanceSettings.url!;
this.withCredentials = Boolean(instanceSettings.withCredentials);
this.defaultEditor = instanceSettings.jsonData.defaultEditor;
this.cache = new QueryCache({
getTargetSignature: this.getPrometheusTargetSignature.bind(this),
overlapString: instanceSettings.jsonData.incrementalQueryOverlapWindow ?? defaultPrometheusQueryOverlapWindow,
applyInterpolation: this.interpolateString.bind(this),
});
// INHERITED PROPERTIES
this.annotations = PrometheusAnnotationSupport(this);
this.variables = new PrometheusVariableSupport(this, this.templateSrv);
// LANGUAGE PROVIDER
// This needs to be the last thing we initialize.
this.languageProvider = languageProvider ?? new PrometheusLanguageProvider(this);
}
init = async () => {
/**
* Initializes the Prometheus datasource by loading recording rules and checking exemplar availability.
*
* This method performs two key initialization tasks: Loads recording rules from the
* Prometheus API and checks if exemplars are available by testing the exemplars API endpoint.
*/
init = async (): Promise<void> => {
if (!this.disableRecordingRules) {
this.loadRules();
}
this.exemplarsAvailable = await this.areExemplarsAvailable();
};
/**
* Loads recording rules from the Prometheus API and extracts rule mappings.
*
* This method fetches rules from the `/api/v1/rules` endpoint and processes
* them to create a mapping of rule names to their corresponding queries and labels.
* The rules API is experimental, so errors are logged but not thrown.
*/
private async loadRules(): Promise<void> {
try {
const params = {};
const options = { showErrorAlert: false };
const res = await this.metadataRequest('/api/v1/rules', params, options);
const ruleGroups = res.data?.data?.groups;
if (ruleGroups) {
this.ruleMappings = extractRuleMappingFromGroups(ruleGroups);
}
} catch (err) {
console.log('Rules API is experimental. Ignore next error.');
console.error(err);
}
}
/**
* Checks if exemplars are available by testing the exemplars API endpoint.
*
* This method makes a test request to the `/api/v1/query_exemplars` endpoint to determine
* if the Prometheus instance supports exemplars. The test uses a simple query with a
* 30-minute time range. If the request succeeds with a 'success' status, exemplars
* are considered available. Errors are caught and return false to avoid breaking
* the datasource initialization.
*/
private async areExemplarsAvailable(): Promise<boolean> {
try {
const params = {
query: 'test',
start: dateTime().subtract(30, 'minutes').valueOf().toString(),
end: dateTime().valueOf().toString(),
};
const options = { showErrorAlert: false };
const res = await this.metadataRequest('/api/v1/query_exemplars', params, options);
return res.data.status === 'success';
} catch (err) {
return false;
}
}
getQueryDisplayText(query: PromQuery) {
return query.expr;
}
@ -604,43 +656,6 @@ export class PrometheusDatasource
return getQueryHints(query.expr ?? '', result, this);
}
async loadRules() {
try {
const res = await this.metadataRequest('/api/v1/rules', {}, { showErrorAlert: false });
const groups = res.data?.data?.groups;
if (groups) {
this.ruleMappings = extractRuleMappingFromGroups(groups);
}
} catch (e) {
console.log('Rules API is experimental. Ignore next error.');
console.error(e);
}
}
async areExemplarsAvailable() {
try {
const res = await this.metadataRequest(
'/api/v1/query_exemplars',
{
query: 'test',
start: dateTime().subtract(30, 'minutes').valueOf().toString(),
end: dateTime().valueOf().toString(),
},
{
// Avoid alerting the user if this test fails
showErrorAlert: false,
}
);
if (res.data.status === 'success') {
return true;
}
return false;
} catch (err) {
return false;
}
}
modifyQuery(query: PromQuery, action: QueryFixAction): PromQuery {
let expression = query.expr ?? '';
switch (action.type) {

@ -20,7 +20,7 @@ export { PromVariableQueryEditor } from './components/VariableQueryEditor';
// Main export
export { ConfigEditor } from './configuration/ConfigEditor';
export { overhaulStyles, validateInput, docsTip } from './configuration/shared/utils';
export { PROM_CONFIG_LABEL_WIDTH } from './constants';
export { PROM_CONFIG_LABEL_WIDTH, InstantQueryRefIdIndex } from './constants';
// The parts
export { AlertingSettingsOverhaul } from './configuration/AlertingSettingsOverhaul';
export { DataSourceHttpSettingsOverhaul } from './configuration/DataSourceHttpSettingsOverhaul';
@ -54,7 +54,7 @@ export { MetricsModal } from './querybuilder/components/metrics-modal/MetricsMod
// SRC/
// Main export
export { PrometheusDatasource, InstantQueryRefIdIndex } from './datasource';
export { PrometheusDatasource } from './datasource';
// The parts
export { addLabelToQuery } from './add_label_to_query';
export { type QueryEditorMode, type PromQueryFormat, type Prometheus } from './dataquery';

Loading…
Cancel
Save