diff --git a/packages/grafana-prometheus/src/datasource.ts b/packages/grafana-prometheus/src/datasource.ts index 9a1110afc0f..492919dfd4c 100644 --- a/packages/grafana-prometheus/src/datasource.ts +++ b/packages/grafana-prometheus/src/datasource.ts @@ -42,9 +42,11 @@ import { import { addLabelToQuery } from './add_label_to_query'; import { PrometheusAnnotationSupport } from './annotations'; -import PrometheusLanguageProvider, { +import { exportToAbstractQuery, importFromAbstractQuery, + PrometheusLanguageProvider, + PrometheusLanguageProviderInterface, SUGGESTIONS_LIMIT, } from './language_provider'; import { expandRecordingRules, getPrometheusTime, getRangeSnapInterval } from './language_utils'; @@ -92,7 +94,7 @@ export class PrometheusDatasource withCredentials: boolean; interval: string; httpMethod: string; - languageProvider: PrometheusLanguageProvider; + languageProvider: PrometheusLanguageProviderInterface; exemplarTraceIdDestinations: ExemplarTraceIdDestination[] | undefined; lookupsDisabled: boolean; customQueryParameters: URLSearchParams; @@ -109,7 +111,7 @@ export class PrometheusDatasource constructor( instanceSettings: DataSourceInstanceSettings, private readonly templateSrv: TemplateSrv = getTemplateSrv(), - languageProvider?: PrometheusLanguageProvider + languageProvider?: PrometheusLanguageProviderInterface ) { super(instanceSettings); diff --git a/packages/grafana-prometheus/src/language_provider.test.ts b/packages/grafana-prometheus/src/language_provider.test.ts index 54ce8ef58da..150416ea8a6 100644 --- a/packages/grafana-prometheus/src/language_provider.test.ts +++ b/packages/grafana-prometheus/src/language_provider.test.ts @@ -10,7 +10,7 @@ import LanguageProvider, { importFromAbstractQuery, processSeries, removeQuotesIfExist, - PrometheusLanguageProvider, + PrometheusLanguageProviderInterface, } from './language_provider'; import { getPrometheusTime, getRangeSnapInterval } from './language_utils'; import { PrometheusCacheLevel, PromQuery } from './types'; @@ -35,7 +35,7 @@ const getMockTimeRange = (): TimeRange => { const getTimeRangeParams = ( timRange: TimeRange, - override?: Partial<{ start: string; end: string }>, + override?: Partial<{ start: string; end: string }> ): { start: string; end: string } => ({ start: fromPrometheusTimeString, end: toPrometheusTimeString, @@ -57,7 +57,7 @@ const verifyRequestParams = ( requestSpy: jest.SpyInstance, expectedUrl: string, expectedParams: unknown, - expectedOptions?: unknown, + expectedOptions?: unknown ) => { expect(requestSpy).toHaveBeenCalled(); expect(requestSpy).toHaveBeenCalledWith(expectedUrl, expect.objectContaining(expectedParams), expectedOptions); @@ -216,7 +216,7 @@ describe('Prometheus Language Provider', () => { .valueOf() / 1000 ).toString(), }, - { headers: { 'X-Grafana-Cache': `private, max-age=${timeSnapMinutes * 60}` } }, + { headers: { 'X-Grafana-Cache': `private, max-age=${timeSnapMinutes * 60}` } } ); }); }); @@ -633,7 +633,7 @@ describe('Prometheus Language Provider', () => { undefined, // omitting scopes parameter [{ key: 'instance', operator: '=', value: 'localhost' }], 'metric', - 100, + 100 ); expect(requestSpy).toHaveBeenCalled(); @@ -690,13 +690,13 @@ describe('Prometheus Language Provider', () => { expect(requestSpy).toHaveBeenCalled(); expect(requestSpy.mock.calls[0][2]?.headers).toHaveProperty('X-Grafana-Cache'); expect(requestSpy.mock.calls[0][2]?.headers?.['X-Grafana-Cache']).toContain( - `private, max-age=${timeSnapMinutes * 60}`, + `private, max-age=${timeSnapMinutes * 60}` ); }); }); describe('New PrometheusLanguageProvider Interface', () => { - let provider: PrometheusLanguageProvider; + let provider: PrometheusLanguageProviderInterface; beforeEach(() => { provider = new LanguageProvider(defaultDatasource); @@ -737,7 +737,7 @@ describe('Prometheus Language Provider', () => { type: 'counter', help: 'help text', }, - }), + }) ); }); diff --git a/packages/grafana-prometheus/src/language_provider.ts b/packages/grafana-prometheus/src/language_provider.ts index d676c630979..9c0e2654bfd 100644 --- a/packages/grafana-prometheus/src/language_provider.ts +++ b/packages/grafana-prometheus/src/language_provider.ts @@ -167,26 +167,13 @@ export interface PrometheusLegacyLanguageProvider { fetchDefaultSeries: (timeRange: TimeRange) => Promise<{}>; } -export interface PrometheusLanguageProvider extends PrometheusBaseLanguageProvider, PrometheusLegacyLanguageProvider { - retrieveMetricsMetadata: () => PromMetricsMetadata | undefined; - retrieveHistogramMetrics: () => string[]; - retrieveMetrics: () => string[]; - retrieveLabelKeys: () => string[]; - - queryMetricsMetadata: () => Promise; - queryLabelKeys: (timeRange: TimeRange, match?: string, limit?: string) => Promise; - queryLabelValues: (timeRange: TimeRange, labelKey: string, match?: string, limit?: string) => Promise; -} - -export default class PromQlLanguageProvider extends LanguageProvider implements PrometheusLanguageProvider { +/** + * @deprecated Use PrometheusLanguageProviderInterface and PrometheusLanguageProvider class instead. + */ +export default class PromQlLanguageProvider extends LanguageProvider implements PrometheusLegacyLanguageProvider { declare startTask: Promise; declare labelFetchTs: number; - private _metricsMetadata?: PromMetricsMetadata; - private _histogramMetrics: string[] = []; - private _metrics: string[] = []; - private _labelKeys: string[] = []; - datasource: PrometheusDatasource; histogramMetrics: string[]; @@ -227,171 +214,6 @@ export default class PromQlLanguageProvider extends LanguageProvider implements return Promise.all([this.loadMetricsMetadata(), this.fetchLabels(timeRange)]); }; - // ====================================================================== - - /** - * Fetches metadata for metrics from Prometheus. - * Sets cache headers based on the configured metadata cache duration. - * - * @returns {Promise} Promise that resolves when metadata has been fetched - */ - private _queryMetadata = async () => { - const secondsInDay = 86400; - const headers = buildCacheHeaders(getDaysToCacheMetadata(this.datasource.cacheLevel) * secondsInDay); - const metadata = await this.request( - API_V1.METADATA, - {}, - { - showErrorAlert: false, - ...headers, - } - ); - return fixSummariesMetadata(metadata); - }; - - // =================================== - // Labels API - // =================================== - - /** - * Fetches all available label keys from Prometheus using labels endpoint. - * Uses the labels endpoint with optional match parameter for filtering. - * - * @param {TimeRange} timeRange - Time range to use for the query - * @param {string} match - Optional label matcher to filter results - * @param {string} limit - Maximum number of results to return - * @returns {Promise} Array of label keys sorted alphabetically - */ - private _withLabelsApiFetchLabelKeys = async ( - timeRange: TimeRange, - match?: string, - limit: string = DEFAULT_SERIES_LIMIT - ): Promise => { - let url = API_V1.LABELS; - const timeParams = getRangeSnapInterval(this.datasource.cacheLevel, timeRange); - const searchParams = { limit, ...timeParams, ...(match ? { 'match[]': match } : {}) }; - - const res = await this.request(url, searchParams, getDefaultCacheHeaders(this.datasource.cacheLevel)); - if (Array.isArray(res)) { - this._labelKeys = res.slice().sort(); - return this._labelKeys.slice(); - } - - return []; - }; - - /** - * Fetches all values for a specific label key from Prometheus using labels values endpoint. - * - * @param {TimeRange} timeRange - Time range to use for the query - * @param {string} labelKey - The label key to fetch values for - * @param {string} match - Optional label matcher to filter results - * @param {string} limit - Maximum number of results to return - * @returns {Promise} Array of label values - */ - private _withLabelsApiFetchLabelValues = async ( - timeRange: TimeRange, - labelKey: string, - match?: string, - limit: string = DEFAULT_SERIES_LIMIT - ): Promise => { - const timeParams = this.datasource.getAdjustedInterval(timeRange); - const searchParams = { limit, ...timeParams, ...(match ? { 'match[]': match } : {}) }; - const interpolatedName = this.datasource.interpolateString(labelKey); - const interpolatedAndEscapedName = escapeForUtf8Support(removeQuotesIfExist(interpolatedName)); - const url = API_V1.LABELS_VALUES(interpolatedAndEscapedName); - const value = await this.request(url, searchParams, getDefaultCacheHeaders(this.datasource.cacheLevel)); - return value ?? []; - }; - - // =================================== - // Series API - // =================================== - - /** - * Fetches all time series that match a specific label matcher using series endpoint. - * - * @param {TimeRange} timeRange - Time range to use for the query - * @param {string} match - Label matcher to filter time series - * @param {string} limit - Maximum number of series to return - */ - private _withSeriesApiFetchAllSeries = async ( - timeRange: TimeRange, - match: string, - limit: string = DEFAULT_SERIES_LIMIT - ) => { - const timeParams = this.datasource.getTimeRangeParams(timeRange); - const searchParams = { ...timeParams, 'match[]': match, limit }; - return await this.request(API_V1.SERIES, searchParams, getDefaultCacheHeaders(this.datasource.cacheLevel)); - }; - - private _withSeriesApiFetchLabelKeys = async ( - timeRange: TimeRange, - match: string, - limit: string = DEFAULT_SERIES_LIMIT - ): Promise => { - const series = await this._withSeriesApiFetchAllSeries(timeRange, match, limit); - const { labelKeys } = processSeries(series); - return labelKeys; - }; - - private _withSeriesApiFetchLabelValues = async ( - timeRange: TimeRange, - labelKey: string, - match: string, - limit: string = DEFAULT_SERIES_LIMIT - ): Promise => { - const series = await this._withSeriesApiFetchAllSeries(timeRange, match, limit); - const { labelValues } = processSeries(series, labelKey); - return labelValues; - }; - - // ====================================================================== - - public retrieveMetricsMetadata = (): PromMetricsMetadata | undefined => { - return this._metricsMetadata; - }; - - public retrieveHistogramMetrics = (): string[] => { - return this._histogramMetrics; - }; - - public retrieveMetrics = (): string[] => { - return this._metrics; - }; - - public retrieveLabelKeys = (): string[] => { - return this._labelKeys; - }; - - public queryMetricsMetadata = async (): Promise => { - this._metricsMetadata = await this._queryMetadata(); - return this._metricsMetadata; - }; - - public queryLabelKeys = async (timeRange: TimeRange, match?: string, limit?: string): Promise => { - if (this.datasource.hasLabelsMatchAPISupport()) { - return await this._withLabelsApiFetchLabelKeys(timeRange, match, limit); - } - - return await this._withSeriesApiFetchLabelKeys(timeRange, match ?? MATCH_ALL_LABELS, limit); - }; - - public queryLabelValues = async ( - timeRange: TimeRange, - labelKey: string, - match?: string, - limit?: string - ): Promise => { - if (this.datasource.hasLabelsMatchAPISupport()) { - return await this._withLabelsApiFetchLabelValues(timeRange, labelKey, match, limit); - } - - return await this._withSeriesApiFetchLabelValues(timeRange, labelKey, match ?? MATCH_ALL_LABELS, limit); - }; - - // ====================================================================== - async loadMetricsMetadata() { const secondsInDay = 86400; const headers = buildCacheHeaders(getDaysToCacheMetadata(this.datasource.cacheLevel) * secondsInDay); @@ -689,6 +511,185 @@ export default class PromQlLanguageProvider extends LanguageProvider implements }; } +export interface PrometheusLanguageProviderInterface + extends PrometheusBaseLanguageProvider, + PrometheusLegacyLanguageProvider { + retrieveMetricsMetadata: () => PromMetricsMetadata | undefined; + retrieveHistogramMetrics: () => string[]; + retrieveMetrics: () => string[]; + retrieveLabelKeys: () => string[]; + + queryMetricsMetadata: () => Promise; + queryLabelKeys: (timeRange: TimeRange, match?: string, limit?: string) => Promise; + queryLabelValues: (timeRange: TimeRange, labelKey: string, match?: string, limit?: string) => Promise; +} + +export class PrometheusLanguageProvider extends PromQlLanguageProvider implements PrometheusLanguageProviderInterface { + private _metricsMetadata?: PromMetricsMetadata; + private _histogramMetrics: string[] = []; + private _metrics: string[] = []; + private _labelKeys: string[] = []; + + /** + * Fetches metadata for metrics from Prometheus. + * Sets cache headers based on the configured metadata cache duration. + * + * @returns {Promise} Promise that resolves when metadata has been fetched + */ + private _queryMetadata = async () => { + const secondsInDay = 86400; + const headers = buildCacheHeaders(getDaysToCacheMetadata(this.datasource.cacheLevel) * secondsInDay); + const metadata = await this.request( + API_V1.METADATA, + {}, + { + showErrorAlert: false, + ...headers, + } + ); + return fixSummariesMetadata(metadata); + }; + + // =================================== + // Labels API + // =================================== + + /** + * Fetches all available label keys from Prometheus using labels endpoint. + * Uses the labels endpoint with optional match parameter for filtering. + * + * @param {TimeRange} timeRange - Time range to use for the query + * @param {string} match - Optional label matcher to filter results + * @param {string} limit - Maximum number of results to return + * @returns {Promise} Array of label keys sorted alphabetically + */ + private _withLabelsApiFetchLabelKeys = async ( + timeRange: TimeRange, + match?: string, + limit: string = DEFAULT_SERIES_LIMIT + ): Promise => { + let url = API_V1.LABELS; + const timeParams = getRangeSnapInterval(this.datasource.cacheLevel, timeRange); + const searchParams = { limit, ...timeParams, ...(match ? { 'match[]': match } : {}) }; + + const res = await this.request(url, searchParams, getDefaultCacheHeaders(this.datasource.cacheLevel)); + if (Array.isArray(res)) { + this._labelKeys = res.slice().sort(); + return this._labelKeys.slice(); + } + + return []; + }; + + /** + * Fetches all values for a specific label key from Prometheus using labels values endpoint. + * + * @param {TimeRange} timeRange - Time range to use for the query + * @param {string} labelKey - The label key to fetch values for + * @param {string} match - Optional label matcher to filter results + * @param {string} limit - Maximum number of results to return + * @returns {Promise} Array of label values + */ + private _withLabelsApiFetchLabelValues = async ( + timeRange: TimeRange, + labelKey: string, + match?: string, + limit: string = DEFAULT_SERIES_LIMIT + ): Promise => { + const timeParams = this.datasource.getAdjustedInterval(timeRange); + const searchParams = { limit, ...timeParams, ...(match ? { 'match[]': match } : {}) }; + const interpolatedName = this.datasource.interpolateString(labelKey); + const interpolatedAndEscapedName = escapeForUtf8Support(removeQuotesIfExist(interpolatedName)); + const url = API_V1.LABELS_VALUES(interpolatedAndEscapedName); + const value = await this.request(url, searchParams, getDefaultCacheHeaders(this.datasource.cacheLevel)); + return value ?? []; + }; + + // =================================== + // Series API + // =================================== + + /** + * Fetches all time series that match a specific label matcher using series endpoint. + * + * @param {TimeRange} timeRange - Time range to use for the query + * @param {string} match - Label matcher to filter time series + * @param {string} limit - Maximum number of series to return + */ + private _withSeriesApiFetchAllSeries = async ( + timeRange: TimeRange, + match: string, + limit: string = DEFAULT_SERIES_LIMIT + ) => { + const timeParams = this.datasource.getTimeRangeParams(timeRange); + const searchParams = { ...timeParams, 'match[]': match, limit }; + return await this.request(API_V1.SERIES, searchParams, getDefaultCacheHeaders(this.datasource.cacheLevel)); + }; + + private _withSeriesApiFetchLabelKeys = async ( + timeRange: TimeRange, + match: string, + limit: string = DEFAULT_SERIES_LIMIT + ): Promise => { + const series = await this._withSeriesApiFetchAllSeries(timeRange, match, limit); + const { labelKeys } = processSeries(series); + return labelKeys; + }; + + private _withSeriesApiFetchLabelValues = async ( + timeRange: TimeRange, + labelKey: string, + match: string, + limit: string = DEFAULT_SERIES_LIMIT + ): Promise => { + const series = await this._withSeriesApiFetchAllSeries(timeRange, match, limit); + const { labelValues } = processSeries(series, labelKey); + return labelValues; + }; + + public retrieveMetricsMetadata = (): PromMetricsMetadata | undefined => { + return this._metricsMetadata; + }; + + public retrieveHistogramMetrics = (): string[] => { + return this._histogramMetrics; + }; + + public retrieveMetrics = (): string[] => { + return this._metrics; + }; + + public retrieveLabelKeys = (): string[] => { + return this._labelKeys; + }; + + public queryMetricsMetadata = async (): Promise => { + this._metricsMetadata = await this._queryMetadata(); + return this._metricsMetadata; + }; + + public queryLabelKeys = async (timeRange: TimeRange, match?: string, limit?: string): Promise => { + if (this.datasource.hasLabelsMatchAPISupport()) { + return await this._withLabelsApiFetchLabelKeys(timeRange, match, limit); + } + + return await this._withSeriesApiFetchLabelKeys(timeRange, match ?? MATCH_ALL_LABELS, limit); + }; + + public queryLabelValues = async ( + timeRange: TimeRange, + labelKey: string, + match?: string, + limit?: string + ): Promise => { + if (this.datasource.hasLabelsMatchAPISupport()) { + return await this._withLabelsApiFetchLabelValues(timeRange, labelKey, match, limit); + } + + return await this._withSeriesApiFetchLabelValues(timeRange, labelKey, match ?? MATCH_ALL_LABELS, limit); + }; +} + export const importFromAbstractQuery = (labelBasedQuery: AbstractQuery): PromQuery => { return toPromLikeQuery(labelBasedQuery); };