From bf7301e485e243561fd4fc2280fa9a620ed04317 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Wed, 9 Jun 2021 14:18:54 +0200 Subject: [PATCH] Postgres/MySQL/MSSQL: Fix so that numeric/non-string values are returned from query variables (#35411) Fixes a problem with query variables where SQL query returning for example only numeric values didn't populate the query variables with values. Fixes #35391 --- .../datasource/mssql/response_parser.ts | 67 +++--------------- .../datasource/mssql/specs/datasource.test.ts | 46 +++++++++++- .../datasource/mysql/response_parser.ts | 67 +++--------------- .../datasource/mysql/specs/datasource.test.ts | 42 ++++++++++- .../datasource/postgres/response_parser.ts | 70 +++---------------- .../postgres/specs/datasource.test.ts | 41 ++++++++++- 6 files changed, 148 insertions(+), 185 deletions(-) diff --git a/public/app/plugins/datasource/mssql/response_parser.ts b/public/app/plugins/datasource/mssql/response_parser.ts index 559bfdcf5e5..e0580789d5e 100644 --- a/public/app/plugins/datasource/mssql/response_parser.ts +++ b/public/app/plugins/datasource/mssql/response_parser.ts @@ -1,5 +1,4 @@ -import { map } from 'lodash'; -import { AnnotationEvent, DataFrame, FieldType, MetricFindValue } from '@grafana/data'; +import { AnnotationEvent, DataFrame, MetricFindValue } from '@grafana/data'; import { BackendDataSourceResponse, toDataQueryResponse, FetchResponse } from '@grafana/runtime'; export default class ResponseParser { @@ -21,16 +20,13 @@ export default class ResponseParser { values.push({ text: '' + textField.values.get(i), value: '' + valueField.values.get(i) }); } } else { - const textFields = frame.fields.filter((f) => f.type === FieldType.string); - if (textFields) { - values.push( - ...textFields - .flatMap((f) => f.values.toArray()) - .map((v) => ({ - text: '' + v, - })) - ); - } + values.push( + ...frame.fields + .flatMap((f) => f.values.toArray()) + .map((v) => ({ + text: v, + })) + ); } return Array.from(new Set(values.map((v) => v.text))).map((text) => ({ @@ -39,53 +35,6 @@ export default class ResponseParser { })); } - transformToKeyValueList(rows: any, textColIndex: number, valueColIndex: number): MetricFindValue[] { - const res = []; - - for (let i = 0; i < rows.length; i++) { - if (!this.containsKey(res, rows[i][textColIndex])) { - res.push({ text: rows[i][textColIndex], value: rows[i][valueColIndex] }); - } - } - - return res; - } - - transformToSimpleList(rows: any): MetricFindValue[] { - const res = []; - - for (let i = 0; i < rows.length; i++) { - for (let j = 0; j < rows[i].length; j++) { - res.push(rows[i][j]); - } - } - - const unique = Array.from(new Set(res)); - - return map(unique, (value) => { - return { text: value }; - }); - } - - findColIndex(columns: any[], colName: string) { - for (let i = 0; i < columns.length; i++) { - if (columns[i].text === colName) { - return i; - } - } - - return -1; - } - - containsKey(res: any[], key: any) { - for (let i = 0; i < res.length; i++) { - if (res[i].text === key) { - return true; - } - } - return false; - } - async transformAnnotationResponse(options: any, data: BackendDataSourceResponse): Promise { const frames = toDataQueryResponse({ data: data }).data as DataFrame[]; const frame = frames[0]; diff --git a/public/app/plugins/datasource/mssql/specs/datasource.test.ts b/public/app/plugins/datasource/mssql/specs/datasource.test.ts index cdcddcc6017..2376a669d4a 100644 --- a/public/app/plugins/datasource/mssql/specs/datasource.test.ts +++ b/public/app/plugins/datasource/mssql/specs/datasource.test.ts @@ -84,7 +84,7 @@ describe('MSSQLDatasource', () => { }); }); - describe('When performing metricFindQuery', () => { + describe('When performing metricFindQuery that returns multiple string fields', () => { let results: MetricFindValue[]; const query = 'select * from atable'; const response = { @@ -156,6 +156,50 @@ describe('MSSQLDatasource', () => { }); }); + describe('When performing metricFindQuery without key, value columns', () => { + let results: any; + const query = 'select id, values from atable'; + const response = { + results: { + tempvar: { + refId: 'tempvar', + frames: [ + dataFrameToJSON( + new MutableDataFrame({ + fields: [ + { name: 'id', values: [1, 2, 3] }, + { name: 'values', values: ['test1', 'test2', 'test3'] }, + ], + meta: { + executedQueryString: 'select id, values from atable', + }, + }) + ), + ], + }, + }, + }; + + beforeEach(() => { + fetchMock.mockImplementation(() => of(createFetchResponse(response))); + + return ctx.ds.metricFindQuery(query).then((data: any) => { + results = data; + }); + }); + + it('should return list of all field values as text', () => { + expect(results).toEqual([ + { text: 1 }, + { text: 2 }, + { text: 3 }, + { text: 'test1' }, + { text: 'test2' }, + { text: 'test3' }, + ]); + }); + }); + describe('When performing metricFindQuery with key, value columns and with duplicate keys', () => { let results: any; const query = 'select * from atable'; diff --git a/public/app/plugins/datasource/mysql/response_parser.ts b/public/app/plugins/datasource/mysql/response_parser.ts index be82c9c1fc3..ac946ad12dc 100644 --- a/public/app/plugins/datasource/mysql/response_parser.ts +++ b/public/app/plugins/datasource/mysql/response_parser.ts @@ -1,5 +1,4 @@ -import { map } from 'lodash'; -import { AnnotationEvent, DataFrame, FieldType, MetricFindValue } from '@grafana/data'; +import { AnnotationEvent, DataFrame, MetricFindValue } from '@grafana/data'; import { BackendDataSourceResponse, FetchResponse, toDataQueryResponse } from '@grafana/runtime'; export default class ResponseParser { @@ -21,16 +20,13 @@ export default class ResponseParser { values.push({ text: '' + textField.values.get(i), value: '' + valueField.values.get(i) }); } } else { - const textFields = frame.fields.filter((f) => f.type === FieldType.string); - if (textFields) { - values.push( - ...textFields - .flatMap((f) => f.values.toArray()) - .map((v) => ({ - text: '' + v, - })) - ); - } + values.push( + ...frame.fields + .flatMap((f) => f.values.toArray()) + .map((v) => ({ + text: v, + })) + ); } return Array.from(new Set(values.map((v) => v.text))).map((text) => ({ @@ -39,53 +35,6 @@ export default class ResponseParser { })); } - transformToKeyValueList(rows: any, textColIndex: number, valueColIndex: number): MetricFindValue[] { - const res = []; - - for (let i = 0; i < rows.length; i++) { - if (!this.containsKey(res, rows[i][textColIndex])) { - res.push({ text: rows[i][textColIndex], value: rows[i][valueColIndex] }); - } - } - - return res; - } - - transformToSimpleList(rows: any): MetricFindValue[] { - const res = []; - - for (let i = 0; i < rows.length; i++) { - for (let j = 0; j < rows[i].length; j++) { - res.push(rows[i][j]); - } - } - - const unique = Array.from(new Set(res)); - - return map(unique, (value) => { - return { text: value }; - }); - } - - findColIndex(columns: any[], colName: string) { - for (let i = 0; i < columns.length; i++) { - if (columns[i].text === colName) { - return i; - } - } - - return -1; - } - - containsKey(res: any[], key: any) { - for (let i = 0; i < res.length; i++) { - if (res[i].text === key) { - return true; - } - } - return false; - } - async transformAnnotationResponse(options: any, data: BackendDataSourceResponse): Promise { const frames = toDataQueryResponse({ data: data }).data as DataFrame[]; const frame = frames[0]; diff --git a/public/app/plugins/datasource/mysql/specs/datasource.test.ts b/public/app/plugins/datasource/mysql/specs/datasource.test.ts index 7b2f3998673..51f085ab41e 100644 --- a/public/app/plugins/datasource/mysql/specs/datasource.test.ts +++ b/public/app/plugins/datasource/mysql/specs/datasource.test.ts @@ -121,7 +121,7 @@ describe('MySQLDatasource', () => { }); }); - describe('When performing metricFindQuery', () => { + describe('When performing metricFindQuery that returns multiple string fields', () => { const query = 'select * from atable'; const response = { results: { @@ -144,7 +144,7 @@ describe('MySQLDatasource', () => { }, }; - it('should return list of all column values', async () => { + it('should return list of all string field values', async () => { const { ds } = setupTextContext(response); const results = await ds.metricFindQuery(query, {}); @@ -257,6 +257,44 @@ describe('MySQLDatasource', () => { }); }); + describe('When performing metricFindQuery without key, value columns', () => { + const query = 'select id, values from atable'; + const response = { + results: { + tempvar: { + refId: 'tempvar', + frames: [ + dataFrameToJSON( + new MutableDataFrame({ + fields: [ + { name: 'id', values: [1, 2, 3] }, + { name: 'values', values: ['test1', 'test2', 'test3'] }, + ], + meta: { + executedQueryString: 'select id, values from atable', + }, + }) + ), + ], + }, + }, + }; + + it('should return list of all field values as text', async () => { + const { ds } = setupTextContext(response); + const results = await ds.metricFindQuery(query, {}); + + expect(results).toEqual([ + { text: 1 }, + { text: 2 }, + { text: 3 }, + { text: 'test1' }, + { text: 'test2' }, + { text: 'test3' }, + ]); + }); + }); + describe('When performing metricFindQuery with key, value columns and with duplicate keys', () => { const query = 'select * from atable'; const response = { diff --git a/public/app/plugins/datasource/postgres/response_parser.ts b/public/app/plugins/datasource/postgres/response_parser.ts index 53c20e9fde0..f0e40ef01ad 100644 --- a/public/app/plugins/datasource/postgres/response_parser.ts +++ b/public/app/plugins/datasource/postgres/response_parser.ts @@ -1,6 +1,5 @@ -import { AnnotationEvent, DataFrame, FieldType, MetricFindValue } from '@grafana/data'; +import { AnnotationEvent, DataFrame, MetricFindValue } from '@grafana/data'; import { BackendDataSourceResponse, FetchResponse, toDataQueryResponse } from '@grafana/runtime'; -import { map } from 'lodash'; export default class ResponseParser { transformMetricFindResponse(raw: FetchResponse): MetricFindValue[] { @@ -21,16 +20,13 @@ export default class ResponseParser { values.push({ text: '' + textField.values.get(i), value: '' + valueField.values.get(i) }); } } else { - const textFields = frame.fields.filter((f) => f.type === FieldType.string); - if (textFields) { - values.push( - ...textFields - .flatMap((f) => f.values.toArray()) - .map((v) => ({ - text: '' + v, - })) - ); - } + values.push( + ...frame.fields + .flatMap((f) => f.values.toArray()) + .map((v) => ({ + text: v, + })) + ); } return Array.from(new Set(values.map((v) => v.text))).map((text) => ({ @@ -39,56 +35,6 @@ export default class ResponseParser { })); } - transformToKeyValueList(rows: any, textColIndex: number, valueColIndex: number) { - const res = []; - - for (let i = 0; i < rows.length; i++) { - if (!this.containsKey(res, rows[i][textColIndex])) { - res.push({ - text: rows[i][textColIndex], - value: rows[i][valueColIndex], - }); - } - } - - return res; - } - - transformToSimpleList(rows: any[][]) { - const res = []; - - for (let i = 0; i < rows.length; i++) { - for (let j = 0; j < rows[i].length; j++) { - res.push(rows[i][j]); - } - } - - const unique = Array.from(new Set(res)); - - return map(unique, (value) => { - return { text: value }; - }); - } - - findColIndex(columns: any[], colName: string) { - for (let i = 0; i < columns.length; i++) { - if (columns[i].text === colName) { - return i; - } - } - - return -1; - } - - containsKey(res: any, key: any) { - for (let i = 0; i < res.length; i++) { - if (res[i].text === key) { - return true; - } - } - return false; - } - async transformAnnotationResponse(options: any, data: BackendDataSourceResponse): Promise { const frames = toDataQueryResponse({ data: data }).data as DataFrame[]; const frame = frames[0]; diff --git a/public/app/plugins/datasource/postgres/specs/datasource.test.ts b/public/app/plugins/datasource/postgres/specs/datasource.test.ts index 77920fbde97..f1eba1b3094 100644 --- a/public/app/plugins/datasource/postgres/specs/datasource.test.ts +++ b/public/app/plugins/datasource/postgres/specs/datasource.test.ts @@ -338,8 +338,8 @@ describe('PostgreSQLDatasource', () => { }); }); - describe('When performing metricFindQuery', () => { - it('should return list of all column values', async () => { + describe('When performing metricFindQuery that returns multiple string fields', () => { + it('should return list of all string field values', async () => { const query = 'select * from atable'; const response = { results: { @@ -487,6 +487,43 @@ describe('PostgreSQLDatasource', () => { }); }); + describe('When performing metricFindQuery without key, value columns', () => { + it('should return list of all field values as text', async () => { + const query = 'select id, values from atable'; + const response = { + results: { + tempvar: { + refId: 'tempvar', + frames: [ + dataFrameToJSON( + new MutableDataFrame({ + fields: [ + { name: 'id', values: [1, 2, 3] }, + { name: 'values', values: ['test1', 'test2', 'test3'] }, + ], + meta: { + executedQueryString: 'select id, values from atable', + }, + }) + ), + ], + }, + }, + }; + const { ds } = setupTestContext(response); + const results = await ds.metricFindQuery(query, {}); + + expect(results).toEqual([ + { text: 1 }, + { text: 2 }, + { text: 3 }, + { text: 'test1' }, + { text: 'test2' }, + { text: 'test3' }, + ]); + }); + }); + describe('When performing metricFindQuery with key, value columns and with duplicate keys', () => { it('should return list of unique keys', async () => { const query = 'select * from atable';