diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/responseParser.test.ts b/public/app/plugins/datasource/grafana-postgresql-datasource/responseParser.test.ts index 64b4b864029..24c1fee3e42 100644 --- a/public/app/plugins/datasource/grafana-postgresql-datasource/responseParser.test.ts +++ b/public/app/plugins/datasource/grafana-postgresql-datasource/responseParser.test.ts @@ -17,17 +17,21 @@ describe('transformMetricFindResponse function', () => { const result = transformMetricFindResponse(frame); - // With the new logic, first field is text/value and additional fields are properties - // After deduplication, we get 1 unique item since all 'a' values deduplicate - // Fields named 'value' are not added to properties to avoid conflicts - expect(result).toHaveLength(1); - expect(result[0]).toEqual({ - text: 'a', - value: 'a', - }); + // All values from both fields are added with properties from the same row + // 150,000 'a' values from 'name' field + 150,000 '1' values from 'value' field = 300,000 total + // After deduplication by text, we get 2 unique items ('a' and '1') + expect(result).toHaveLength(2); + + const textValues = result.map((r) => r.text); + expect(textValues).toContain('a'); + expect(textValues).toContain('1'); // Numbers are converted to strings + + // Check that properties are included + const aEntry = result.find((r) => r.text === 'a'); + expect(aEntry?.properties).toBeDefined(); }); - it('should use first field as text and value with additional fields as properties', () => { + it('should add all values from multiple fields with properties from same row', () => { const frame: DataFrame = { fields: [ { name: 'id', type: FieldType.string, config: {}, values: ['user1', 'user2', 'user3'] }, @@ -44,34 +48,25 @@ describe('transformMetricFindResponse function', () => { const result = transformMetricFindResponse(frame); - expect(result).toHaveLength(3); - expect(result[0]).toEqual({ - text: 'user1', - value: 'user1', - properties: { - email: 'user1@test.com', - role: 'admin', - }, - }); - expect(result[1]).toEqual({ - text: 'user2', - value: 'user2', - properties: { - email: 'user2@test.com', - role: 'user', - }, - }); - expect(result[2]).toEqual({ - text: 'user3', - value: 'user3', - properties: { - email: 'user3@test.com', - role: 'guest', - }, - }); + // All values from all fields are added (3 rows × 3 fields = 9 entries) + expect(result).toHaveLength(9); + + // Value from row 0 of id field gets properties from row 0 of other fields + const user1Entry = result.find((r) => r.text === 'user1'); + expect(user1Entry).toBeDefined(); + expect(user1Entry?.properties).toBeDefined(); + expect(user1Entry?.properties?.email).toBe('user1@test.com'); + expect(user1Entry?.properties?.role).toBe('admin'); + + // Value from row 1 of email field gets properties from row 1 of other fields + const user2EmailEntry = result.find((r) => r.text === 'user2@test.com'); + expect(user2EmailEntry).toBeDefined(); + expect(user2EmailEntry?.properties).toBeDefined(); + expect(user2EmailEntry?.properties?.id).toBe('user2'); + expect(user2EmailEntry?.properties?.role).toBe('user'); }); - it('should handle single field without properties', () => { + it('should handle single field', () => { const frame: DataFrame = { fields: [{ name: 'name', type: FieldType.string, config: {}, values: ['value1', 'value2'] }], length: 2, @@ -80,13 +75,18 @@ describe('transformMetricFindResponse function', () => { const result = transformMetricFindResponse(frame); expect(result).toHaveLength(2); + // With single field, properties include the same field expect(result[0]).toEqual({ text: 'value1', - value: 'value1', + properties: { + name: 'value1', + }, }); expect(result[1]).toEqual({ text: 'value2', - value: 'value2', + properties: { + name: 'value2', + }, }); }); @@ -112,7 +112,7 @@ describe('transformMetricFindResponse function', () => { }); }); - it('should not add fields named "text" or "value" to properties', () => { + it('should skip fields named "text" or "value" in properties', () => { const frame: DataFrame = { fields: [ { name: 'id', type: FieldType.string, config: {}, values: ['item1', 'item2'] }, @@ -125,22 +125,15 @@ describe('transformMetricFindResponse function', () => { const result = transformMetricFindResponse(frame); - expect(result).toHaveLength(2); - // Fields named 'text' and 'value' should not be in properties - expect(result[0]).toEqual({ - text: 'item1', - value: 'item1', - properties: { - description: 'Desc 1', - }, - }); - expect(result[1]).toEqual({ - text: 'item2', - value: 'item2', - properties: { - description: 'Desc 2', - }, - }); + // All values from all fields are added (2 values × 4 fields = 8 entries) + expect(result).toHaveLength(8); + + // Check that 'text' and 'value' fields are not in properties + const item1Entry = result.find((r) => r.text === 'item1'); + expect(item1Entry?.properties).toBeDefined(); + expect(item1Entry?.properties).not.toHaveProperty('text'); + expect(item1Entry?.properties).not.toHaveProperty('value'); + expect(item1Entry?.properties?.description).toBe('Desc 1'); }); it('should add additional fields as properties when __text and __value are present', () => { diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/responseParser.ts b/public/app/plugins/datasource/grafana-postgresql-datasource/responseParser.ts index 5a34f3a4de8..bcb980b8387 100644 --- a/public/app/plugins/datasource/grafana-postgresql-datasource/responseParser.ts +++ b/public/app/plugins/datasource/grafana-postgresql-datasource/responseParser.ts @@ -2,47 +2,70 @@ import { uniqBy } from 'lodash'; import { DataFrame, Field, MetricFindValue } from '@grafana/data'; +const RESERVED_PROPERTY_NAMES = ['text', 'value']; + export function transformMetricFindResponse(frame: DataFrame): MetricFindValue[] { const textField = frame.fields.find((f) => f.name === '__text'); const valueField = frame.fields.find((f) => f.name === '__value'); - let values: MetricFindValue[]; - - if (textField && valueField) { - const additionalFields = frame.fields.filter((f) => f.name !== '__text' && f.name !== '__value'); - values = buildMetricFindValues(textField, valueField, additionalFields); - } else if (frame.fields.length > 0) { - // Support multiple fields by first field as text/value and additional fields as properties - const firstField = frame.fields[0]; - const additionalFields = frame.fields.slice(1); - values = buildMetricFindValues(firstField, firstField, additionalFields); - } else { - values = []; - } + const values = + textField && valueField + ? buildValuesFromTextValueFields(textField, valueField, frame.fields) + : buildValuesFromAllFields(frame.fields); return uniqBy(values, 'text'); } -function buildMetricFindValues(textField: Field, valueField: Field, additionalFields: Field[]): MetricFindValue[] { +function buildValuesFromTextValueFields(textField: Field, valueField: Field, allFields: Field[]): MetricFindValue[] { + const additionalFields = allFields.filter((f) => f.name !== '__text' && f.name !== '__value'); const values: MetricFindValue[] = []; - for (let i = 0; i < textField.values.length; i++) { - const item: MetricFindValue = { - text: '' + textField.values[i], - value: '' + valueField.values[i], - }; - - const properties = buildProperties(additionalFields, i); - if (properties) { - item.properties = properties; - } - - values.push(item); + for (let rowIndex = 0; rowIndex < textField.values.length; rowIndex++) { + values.push( + createMetricFindValue( + '' + textField.values[rowIndex], + '' + valueField.values[rowIndex], + additionalFields, + rowIndex + ) + ); } return values; } +function buildValuesFromAllFields(fields: Field[]): MetricFindValue[] { + const values: MetricFindValue[] = []; + + for (const field of fields) { + for (let rowIndex = 0; rowIndex < field.values.length; rowIndex++) { + values.push(createMetricFindValue(field.values[rowIndex], undefined, fields, rowIndex)); + } + } + + return values; +} + +function createMetricFindValue( + text: unknown, + value: string | undefined, + fields: Field[], + rowIndex: number +): MetricFindValue { + const item: MetricFindValue = { text: '' + text }; + + if (value !== undefined) { + item.value = value; + } + + const properties = buildProperties(fields, rowIndex); + if (properties) { + item.properties = properties; + } + + return item; +} + function buildProperties(fields: Field[], rowIndex: number): Record | undefined { if (fields.length === 0) { return undefined; @@ -51,12 +74,10 @@ function buildProperties(fields: Field[], rowIndex: number): Record = {}; for (const field of fields) { - // Skip fields named 'text' or 'value' to avoid conflicts with top-level fields - if (field.name !== 'text' && field.name !== 'value') { + if (!RESERVED_PROPERTY_NAMES.includes(field.name)) { properties[field.name] = '' + field.values[rowIndex]; } } - // Only return properties object if there are actual properties return Object.keys(properties).length > 0 ? properties : undefined; }