From 084066c7124d07cc1b9ab871cb00f7edad99c9cd Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Tue, 27 Apr 2021 16:16:48 +0200 Subject: [PATCH] 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 --- .../datasource/prometheus/datasource.test.ts | 7 +++- .../datasource/prometheus/datasource.ts | 5 ++- .../prometheus/language_provider.ts | 40 ++++++++++--------- .../prometheus/metric_find_query.test.ts | 2 +- .../prometheus/metric_find_query.ts | 40 +++++++++---------- 5 files changed, 51 insertions(+), 43 deletions(-) diff --git a/public/app/plugins/datasource/prometheus/datasource.test.ts b/public/app/plugins/datasource/prometheus/datasource.test.ts index 93f09c83ad9..87a992fc42b 100644 --- a/public/app/plugins/datasource/prometheus/datasource.test.ts +++ b/public/app/plugins/datasource/prometheus/datasource.test.ts @@ -101,9 +101,10 @@ describe('PrometheusDatasource', () => { describe('Datasource metadata requests', () => { 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[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', () => { const postSettings = cloneDeep(instanceSettings); @@ -117,9 +118,11 @@ describe('PrometheusDatasource', () => { const postSettings = cloneDeep(instanceSettings); postSettings.jsonData.httpMethod = 'POST'; 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[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' }); }); }); diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index 01891cd831a..9a95731b414 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -138,8 +138,9 @@ export class PrometheusDatasource extends DataSourceApi } // Use this for tab completion features, wont publish response to other components - async metadataRequest(url: string) { - const data: any = {}; + async metadataRequest(url: string, params = {}) { + const data: any = params; + for (const [key, value] of this.customQueryParameters) { if (data[key] == null) { data[key] = value; diff --git a/public/app/plugins/datasource/prometheus/language_provider.ts b/public/app/plugins/datasource/prometheus/language_provider.ts index bab45e065bb..c8ae4f535c2 100644 --- a/public/app/plugins/datasource/prometheus/language_provider.ts +++ b/public/app/plugins/datasource/prometheus/language_provider.ts @@ -99,9 +99,9 @@ export default class PromQlLanguageProvider extends LanguageProvider { return PromqlSyntax; } - request = async (url: string, defaultValue: any): Promise => { + request = async (url: string, defaultValue: any, params = {}): Promise => { try { - const res = await this.datasource.metadataRequest(url); + const res = await this.datasource.metadataRequest(url, params); return res.data.data; } catch (error) { console.error(error); @@ -116,13 +116,13 @@ export default class PromQlLanguageProvider extends LanguageProvider { } const tRange = this.datasource.getTimeRange(); - const params = new URLSearchParams({ + const params = { start: tRange['start'].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.processHistogramMetrics(this.metrics); @@ -426,12 +426,12 @@ export default class PromQlLanguageProvider extends LanguageProvider { fetchLabelValues = async (key: string): Promise> => { const tRange = this.datasource.getTimeRange(); - const params = new URLSearchParams({ + const params = { start: tRange['start'].toString(), end: tRange['end'].toString(), - }); - const url = `/api/v1/label/${key}/values?${params.toString()}`; - const data = await this.request(url, []); + }; + const url = `/api/v1/label/${key}/values`; + const data = await this.request(url, [], params); return { [key]: data }; }; @@ -443,23 +443,27 @@ export default class PromQlLanguageProvider extends LanguageProvider { */ fetchSeriesLabels = async (name: string, withName?: boolean): Promise> => { const tRange = this.datasource.getTimeRange(); - const params = new URLSearchParams({ + const urlParams = { 'match[]': name, start: tRange['start'].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. // 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 // when user does not the newest values for a minute if already cached. - params.set('start', roundSecToMin(tRange['start']).toString()); - params.set('end', roundSecToMin(tRange['end']).toString()); - params.append('withName', withName ? 'true' : 'false'); - const cacheKey = `/api/v1/series?${params.toString()}`; + const cacheParams = new URLSearchParams({ + 'match[]': name, + start: roundSecToMin(tRange['start']).toString(), + end: roundSecToMin(tRange['end']).toString(), + withName: withName ? 'true' : 'false', + }); + + const cacheKey = `/api/v1/series?${cacheParams.toString()}`; let value = this.labelsCache.get(cacheKey); if (!value) { - const data = await this.request(url, []); + const data = await this.request(url, [], urlParams); const { values } = processLabels(data, withName); value = values; this.labelsCache.set(cacheKey, value); diff --git a/public/app/plugins/datasource/prometheus/metric_find_query.test.ts b/public/app/plugins/datasource/prometheus/metric_find_query.test.ts index 7c7c88c0ebf..80fe8e8f6df 100644 --- a/public/app/plugins/datasource/prometheus/metric_find_query.test.ts +++ b/public/app/plugins/datasource/prometheus/metric_find_query.test.ts @@ -142,7 +142,7 @@ describe('PrometheusMetricFindQuery', () => { expect(fetchMock).toHaveBeenCalledWith({ method: 'GET', 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, headers: {}, }); diff --git a/public/app/plugins/datasource/prometheus/metric_find_query.ts b/public/app/plugins/datasource/prometheus/metric_find_query.ts index d6b5ce7286e..e80e893e7bb 100644 --- a/public/app/plugins/datasource/prometheus/metric_find_query.ts +++ b/public/app/plugins/datasource/prometheus/metric_find_query.ts @@ -50,14 +50,14 @@ export default class PrometheusMetricFindQuery { labelNamesQuery() { const start = this.datasource.getPrometheusTime(this.range.from, false); const end = this.datasource.getPrometheusTime(this.range.to, true); - const params = new URLSearchParams({ + const params = { start: start.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 { text: value }; }); @@ -71,27 +71,27 @@ export default class PrometheusMetricFindQuery { let url: string; if (!metric) { - const params = new URLSearchParams({ + const params = { start: start.toString(), end: end.toString(), - }); + }; // 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 { text: value }; }); }); } else { - const params = new URLSearchParams({ + const params = { 'match[]': metric, start: start.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) => { return metric[label] || ''; }).filter((label) => { @@ -111,13 +111,13 @@ export default class PrometheusMetricFindQuery { metricNameQuery(metricFilterPattern: string) { const start = this.datasource.getPrometheusTime(this.range.from, false); const end = this.datasource.getPrometheusTime(this.range.to, true); - const params = new URLSearchParams({ + const params = { start: start.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) .filter((metricName) => { const r = new RegExp(metricFilterPattern); @@ -161,16 +161,16 @@ export default class PrometheusMetricFindQuery { metricNameAndLabelsQuery(query: string): Promise { const start = this.datasource.getPrometheusTime(this.range.from, false); const end = this.datasource.getPrometheusTime(this.range.to, true); - const params = new URLSearchParams({ + const params = { 'match[]': query, start: start.toString(), end: end.toString(), - }); + }; - const url = `/api/v1/series?${params.toString()}`; + const url = `/api/v1/series`; 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 { text: self.datasource.getOriginalMetricName(metric),