separation of concerns
This commit is contained in:
@@ -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<PromOptions>,
|
||||
private readonly templateSrv: TemplateSrv = getTemplateSrv(),
|
||||
languageProvider?: PrometheusLanguageProvider
|
||||
languageProvider?: PrometheusLanguageProviderInterface
|
||||
) {
|
||||
super(instanceSettings);
|
||||
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -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<PromMetricsMetadata>;
|
||||
queryLabelKeys: (timeRange: TimeRange, match?: string, limit?: string) => Promise<string[]>;
|
||||
queryLabelValues: (timeRange: TimeRange, labelKey: string, match?: string, limit?: string) => Promise<string[]>;
|
||||
}
|
||||
|
||||
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<any>;
|
||||
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<PromMetricsMetadata>} 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<string[]>} Array of label keys sorted alphabetically
|
||||
*/
|
||||
private _withLabelsApiFetchLabelKeys = async (
|
||||
timeRange: TimeRange,
|
||||
match?: string,
|
||||
limit: string = DEFAULT_SERIES_LIMIT
|
||||
): Promise<string[]> => {
|
||||
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<string[]>} Array of label values
|
||||
*/
|
||||
private _withLabelsApiFetchLabelValues = async (
|
||||
timeRange: TimeRange,
|
||||
labelKey: string,
|
||||
match?: string,
|
||||
limit: string = DEFAULT_SERIES_LIMIT
|
||||
): Promise<string[]> => {
|
||||
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<string[]> => {
|
||||
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<string[]> => {
|
||||
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<PromMetricsMetadata> => {
|
||||
this._metricsMetadata = await this._queryMetadata();
|
||||
return this._metricsMetadata;
|
||||
};
|
||||
|
||||
public queryLabelKeys = async (timeRange: TimeRange, match?: string, limit?: string): Promise<string[]> => {
|
||||
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<string[]> => {
|
||||
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<PromMetricsMetadata>;
|
||||
queryLabelKeys: (timeRange: TimeRange, match?: string, limit?: string) => Promise<string[]>;
|
||||
queryLabelValues: (timeRange: TimeRange, labelKey: string, match?: string, limit?: string) => Promise<string[]>;
|
||||
}
|
||||
|
||||
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<PromMetricsMetadata>} 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<string[]>} Array of label keys sorted alphabetically
|
||||
*/
|
||||
private _withLabelsApiFetchLabelKeys = async (
|
||||
timeRange: TimeRange,
|
||||
match?: string,
|
||||
limit: string = DEFAULT_SERIES_LIMIT
|
||||
): Promise<string[]> => {
|
||||
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<string[]>} Array of label values
|
||||
*/
|
||||
private _withLabelsApiFetchLabelValues = async (
|
||||
timeRange: TimeRange,
|
||||
labelKey: string,
|
||||
match?: string,
|
||||
limit: string = DEFAULT_SERIES_LIMIT
|
||||
): Promise<string[]> => {
|
||||
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<string[]> => {
|
||||
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<string[]> => {
|
||||
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<PromMetricsMetadata> => {
|
||||
this._metricsMetadata = await this._queryMetadata();
|
||||
return this._metricsMetadata;
|
||||
};
|
||||
|
||||
public queryLabelKeys = async (timeRange: TimeRange, match?: string, limit?: string): Promise<string[]> => {
|
||||
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<string[]> => {
|
||||
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);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user