mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Prometheus: Support POST in template variables (#33321)
* If POST, pass params as data not in url string * Fix metricNameAndLabelsQuery * Improve test for new functionality * Add multiple parameters in test scenarios * Change urlParams to object
This commit is contained in:
parent
ed3b87f165
commit
084066c712
@ -101,9 +101,10 @@ describe('PrometheusDatasource', () => {
|
|||||||
|
|
||||||
describe('Datasource metadata requests', () => {
|
describe('Datasource metadata requests', () => {
|
||||||
it('should perform a GET request with the default config', () => {
|
it('should perform a GET request with the default config', () => {
|
||||||
ds.metadataRequest('/foo');
|
ds.metadataRequest('/foo', { bar: 'baz baz', foo: 'foo' });
|
||||||
expect(fetchMock.mock.calls.length).toBe(1);
|
expect(fetchMock.mock.calls.length).toBe(1);
|
||||||
expect(fetchMock.mock.calls[0][0].method).toBe('GET');
|
expect(fetchMock.mock.calls[0][0].method).toBe('GET');
|
||||||
|
expect(fetchMock.mock.calls[0][0].url).toContain('bar=baz%20baz&foo=foo');
|
||||||
});
|
});
|
||||||
it('should still perform a GET request with the DS HTTP method set to POST and not POST-friendly endpoint', () => {
|
it('should still perform a GET request with the DS HTTP method set to POST and not POST-friendly endpoint', () => {
|
||||||
const postSettings = cloneDeep(instanceSettings);
|
const postSettings = cloneDeep(instanceSettings);
|
||||||
@ -117,9 +118,11 @@ describe('PrometheusDatasource', () => {
|
|||||||
const postSettings = cloneDeep(instanceSettings);
|
const postSettings = cloneDeep(instanceSettings);
|
||||||
postSettings.jsonData.httpMethod = 'POST';
|
postSettings.jsonData.httpMethod = 'POST';
|
||||||
const promDs = new PrometheusDatasource(postSettings, templateSrvStub as any, timeSrvStub as any);
|
const promDs = new PrometheusDatasource(postSettings, templateSrvStub as any, timeSrvStub as any);
|
||||||
promDs.metadataRequest('api/v1/series');
|
promDs.metadataRequest('api/v1/series', { bar: 'baz baz', foo: 'foo' });
|
||||||
expect(fetchMock.mock.calls.length).toBe(1);
|
expect(fetchMock.mock.calls.length).toBe(1);
|
||||||
expect(fetchMock.mock.calls[0][0].method).toBe('POST');
|
expect(fetchMock.mock.calls[0][0].method).toBe('POST');
|
||||||
|
expect(fetchMock.mock.calls[0][0].url).not.toContain('bar=baz%20baz&foo=foo');
|
||||||
|
expect(fetchMock.mock.calls[0][0].data).toEqual({ bar: 'baz baz', foo: 'foo' });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -138,8 +138,9 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Use this for tab completion features, wont publish response to other components
|
// Use this for tab completion features, wont publish response to other components
|
||||||
async metadataRequest<T = any>(url: string) {
|
async metadataRequest<T = any>(url: string, params = {}) {
|
||||||
const data: any = {};
|
const data: any = params;
|
||||||
|
|
||||||
for (const [key, value] of this.customQueryParameters) {
|
for (const [key, value] of this.customQueryParameters) {
|
||||||
if (data[key] == null) {
|
if (data[key] == null) {
|
||||||
data[key] = value;
|
data[key] = value;
|
||||||
|
@ -99,9 +99,9 @@ export default class PromQlLanguageProvider extends LanguageProvider {
|
|||||||
return PromqlSyntax;
|
return PromqlSyntax;
|
||||||
}
|
}
|
||||||
|
|
||||||
request = async (url: string, defaultValue: any): Promise<any> => {
|
request = async (url: string, defaultValue: any, params = {}): Promise<any> => {
|
||||||
try {
|
try {
|
||||||
const res = await this.datasource.metadataRequest(url);
|
const res = await this.datasource.metadataRequest(url, params);
|
||||||
return res.data.data;
|
return res.data.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -116,13 +116,13 @@ export default class PromQlLanguageProvider extends LanguageProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tRange = this.datasource.getTimeRange();
|
const tRange = this.datasource.getTimeRange();
|
||||||
const params = new URLSearchParams({
|
const params = {
|
||||||
start: tRange['start'].toString(),
|
start: tRange['start'].toString(),
|
||||||
end: tRange['end'].toString(),
|
end: tRange['end'].toString(),
|
||||||
});
|
};
|
||||||
const url = `/api/v1/label/__name__/values?${params.toString()}`;
|
const url = `/api/v1/label/__name__/values`;
|
||||||
|
|
||||||
this.metrics = await this.request(url, []);
|
this.metrics = await this.request(url, [], params);
|
||||||
this.metricsMetadata = fixSummariesMetadata(await this.request('/api/v1/metadata', {}));
|
this.metricsMetadata = fixSummariesMetadata(await this.request('/api/v1/metadata', {}));
|
||||||
this.processHistogramMetrics(this.metrics);
|
this.processHistogramMetrics(this.metrics);
|
||||||
|
|
||||||
@ -426,12 +426,12 @@ export default class PromQlLanguageProvider extends LanguageProvider {
|
|||||||
|
|
||||||
fetchLabelValues = async (key: string): Promise<Record<string, string[]>> => {
|
fetchLabelValues = async (key: string): Promise<Record<string, string[]>> => {
|
||||||
const tRange = this.datasource.getTimeRange();
|
const tRange = this.datasource.getTimeRange();
|
||||||
const params = new URLSearchParams({
|
const params = {
|
||||||
start: tRange['start'].toString(),
|
start: tRange['start'].toString(),
|
||||||
end: tRange['end'].toString(),
|
end: tRange['end'].toString(),
|
||||||
});
|
};
|
||||||
const url = `/api/v1/label/${key}/values?${params.toString()}`;
|
const url = `/api/v1/label/${key}/values`;
|
||||||
const data = await this.request(url, []);
|
const data = await this.request(url, [], params);
|
||||||
return { [key]: data };
|
return { [key]: data };
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -443,23 +443,27 @@ export default class PromQlLanguageProvider extends LanguageProvider {
|
|||||||
*/
|
*/
|
||||||
fetchSeriesLabels = async (name: string, withName?: boolean): Promise<Record<string, string[]>> => {
|
fetchSeriesLabels = async (name: string, withName?: boolean): Promise<Record<string, string[]>> => {
|
||||||
const tRange = this.datasource.getTimeRange();
|
const tRange = this.datasource.getTimeRange();
|
||||||
const params = new URLSearchParams({
|
const urlParams = {
|
||||||
'match[]': name,
|
'match[]': name,
|
||||||
start: tRange['start'].toString(),
|
start: tRange['start'].toString(),
|
||||||
end: tRange['end'].toString(),
|
end: tRange['end'].toString(),
|
||||||
});
|
};
|
||||||
const url = `/api/v1/series?${params.toString()}`;
|
const url = `/api/v1/series`;
|
||||||
// Cache key is a bit different here. We add the `withName` param and also round up to a minute the intervals.
|
// Cache key is a bit different here. We add the `withName` param and also round up to a minute the intervals.
|
||||||
// The rounding may seem strange but makes relative intervals like now-1h less prone to need separate request every
|
// The rounding may seem strange but makes relative intervals like now-1h less prone to need separate request every
|
||||||
// millisecond while still actually getting all the keys for the correct interval. This still can create problems
|
// millisecond while still actually getting all the keys for the correct interval. This still can create problems
|
||||||
// when user does not the newest values for a minute if already cached.
|
// when user does not the newest values for a minute if already cached.
|
||||||
params.set('start', roundSecToMin(tRange['start']).toString());
|
const cacheParams = new URLSearchParams({
|
||||||
params.set('end', roundSecToMin(tRange['end']).toString());
|
'match[]': name,
|
||||||
params.append('withName', withName ? 'true' : 'false');
|
start: roundSecToMin(tRange['start']).toString(),
|
||||||
const cacheKey = `/api/v1/series?${params.toString()}`;
|
end: roundSecToMin(tRange['end']).toString(),
|
||||||
|
withName: withName ? 'true' : 'false',
|
||||||
|
});
|
||||||
|
|
||||||
|
const cacheKey = `/api/v1/series?${cacheParams.toString()}`;
|
||||||
let value = this.labelsCache.get(cacheKey);
|
let value = this.labelsCache.get(cacheKey);
|
||||||
if (!value) {
|
if (!value) {
|
||||||
const data = await this.request(url, []);
|
const data = await this.request(url, [], urlParams);
|
||||||
const { values } = processLabels(data, withName);
|
const { values } = processLabels(data, withName);
|
||||||
value = values;
|
value = values;
|
||||||
this.labelsCache.set(cacheKey, value);
|
this.labelsCache.set(cacheKey, value);
|
||||||
|
@ -142,7 +142,7 @@ describe('PrometheusMetricFindQuery', () => {
|
|||||||
expect(fetchMock).toHaveBeenCalledWith({
|
expect(fetchMock).toHaveBeenCalledWith({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url:
|
url:
|
||||||
'proxied/api/v1/series?match%5B%5D=metric%7Blabel1%3D%22foo%22%2C+label2%3D%22bar%22%2C+label3%3D%22baz%22%7D&start=1524650400&end=1524654000',
|
'proxied/api/v1/series?match%5B%5D=metric%7Blabel1%3D%22foo%22%2C%20label2%3D%22bar%22%2C%20label3%3D%22baz%22%7D&start=1524650400&end=1524654000',
|
||||||
hideFromInspector: true,
|
hideFromInspector: true,
|
||||||
headers: {},
|
headers: {},
|
||||||
});
|
});
|
||||||
|
@ -50,14 +50,14 @@ export default class PrometheusMetricFindQuery {
|
|||||||
labelNamesQuery() {
|
labelNamesQuery() {
|
||||||
const start = this.datasource.getPrometheusTime(this.range.from, false);
|
const start = this.datasource.getPrometheusTime(this.range.from, false);
|
||||||
const end = this.datasource.getPrometheusTime(this.range.to, true);
|
const end = this.datasource.getPrometheusTime(this.range.to, true);
|
||||||
const params = new URLSearchParams({
|
const params = {
|
||||||
start: start.toString(),
|
start: start.toString(),
|
||||||
end: end.toString(),
|
end: end.toString(),
|
||||||
});
|
};
|
||||||
|
|
||||||
const url = `/api/v1/labels?${params.toString()}`;
|
const url = `/api/v1/labels`;
|
||||||
|
|
||||||
return this.datasource.metadataRequest(url).then((result: any) => {
|
return this.datasource.metadataRequest(url, params).then((result: any) => {
|
||||||
return _map(result.data.data, (value) => {
|
return _map(result.data.data, (value) => {
|
||||||
return { text: value };
|
return { text: value };
|
||||||
});
|
});
|
||||||
@ -71,27 +71,27 @@ export default class PrometheusMetricFindQuery {
|
|||||||
let url: string;
|
let url: string;
|
||||||
|
|
||||||
if (!metric) {
|
if (!metric) {
|
||||||
const params = new URLSearchParams({
|
const params = {
|
||||||
start: start.toString(),
|
start: start.toString(),
|
||||||
end: end.toString(),
|
end: end.toString(),
|
||||||
});
|
};
|
||||||
// return label values globally
|
// return label values globally
|
||||||
url = `/api/v1/label/${label}/values?${params.toString()}`;
|
url = `/api/v1/label/${label}/values`;
|
||||||
|
|
||||||
return this.datasource.metadataRequest(url).then((result: any) => {
|
return this.datasource.metadataRequest(url, params).then((result: any) => {
|
||||||
return _map(result.data.data, (value) => {
|
return _map(result.data.data, (value) => {
|
||||||
return { text: value };
|
return { text: value };
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const params = new URLSearchParams({
|
const params = {
|
||||||
'match[]': metric,
|
'match[]': metric,
|
||||||
start: start.toString(),
|
start: start.toString(),
|
||||||
end: end.toString(),
|
end: end.toString(),
|
||||||
});
|
};
|
||||||
url = `/api/v1/series?${params.toString()}`;
|
url = `/api/v1/series`;
|
||||||
|
|
||||||
return this.datasource.metadataRequest(url).then((result: any) => {
|
return this.datasource.metadataRequest(url, params).then((result: any) => {
|
||||||
const _labels = _map(result.data.data, (metric) => {
|
const _labels = _map(result.data.data, (metric) => {
|
||||||
return metric[label] || '';
|
return metric[label] || '';
|
||||||
}).filter((label) => {
|
}).filter((label) => {
|
||||||
@ -111,13 +111,13 @@ export default class PrometheusMetricFindQuery {
|
|||||||
metricNameQuery(metricFilterPattern: string) {
|
metricNameQuery(metricFilterPattern: string) {
|
||||||
const start = this.datasource.getPrometheusTime(this.range.from, false);
|
const start = this.datasource.getPrometheusTime(this.range.from, false);
|
||||||
const end = this.datasource.getPrometheusTime(this.range.to, true);
|
const end = this.datasource.getPrometheusTime(this.range.to, true);
|
||||||
const params = new URLSearchParams({
|
const params = {
|
||||||
start: start.toString(),
|
start: start.toString(),
|
||||||
end: end.toString(),
|
end: end.toString(),
|
||||||
});
|
};
|
||||||
const url = `/api/v1/label/__name__/values?${params.toString()}`;
|
const url = `/api/v1/label/__name__/values`;
|
||||||
|
|
||||||
return this.datasource.metadataRequest(url).then((result: any) => {
|
return this.datasource.metadataRequest(url, params).then((result: any) => {
|
||||||
return chain(result.data.data)
|
return chain(result.data.data)
|
||||||
.filter((metricName) => {
|
.filter((metricName) => {
|
||||||
const r = new RegExp(metricFilterPattern);
|
const r = new RegExp(metricFilterPattern);
|
||||||
@ -161,16 +161,16 @@ export default class PrometheusMetricFindQuery {
|
|||||||
metricNameAndLabelsQuery(query: string): Promise<MetricFindValue[]> {
|
metricNameAndLabelsQuery(query: string): Promise<MetricFindValue[]> {
|
||||||
const start = this.datasource.getPrometheusTime(this.range.from, false);
|
const start = this.datasource.getPrometheusTime(this.range.from, false);
|
||||||
const end = this.datasource.getPrometheusTime(this.range.to, true);
|
const end = this.datasource.getPrometheusTime(this.range.to, true);
|
||||||
const params = new URLSearchParams({
|
const params = {
|
||||||
'match[]': query,
|
'match[]': query,
|
||||||
start: start.toString(),
|
start: start.toString(),
|
||||||
end: end.toString(),
|
end: end.toString(),
|
||||||
});
|
};
|
||||||
|
|
||||||
const url = `/api/v1/series?${params.toString()}`;
|
const url = `/api/v1/series`;
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
return this.datasource.metadataRequest(url).then((result: any) => {
|
return this.datasource.metadataRequest(url, params).then((result: any) => {
|
||||||
return _map(result.data.data, (metric: { [key: string]: string }) => {
|
return _map(result.data.data, (metric: { [key: string]: string }) => {
|
||||||
return {
|
return {
|
||||||
text: self.datasource.getOriginalMetricName(metric),
|
text: self.datasource.getOriginalMetricName(metric),
|
||||||
|
Loading…
Reference in New Issue
Block a user