Refactor response parser to enhance metric transformation logic
- Updated the `transformMetricFindResponse` function to handle multiple fields more effectively, ensuring all values are included in the output. - Introduced helper functions for better code organization and readability. - Adjusted tests to reflect changes in the transformation logic, ensuring accurate validation of properties and deduplication behavior.
This commit is contained in:
+47
-54
@@ -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', () => {
|
||||
|
||||
@@ -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<string, string> | undefined {
|
||||
if (fields.length === 0) {
|
||||
return undefined;
|
||||
@@ -51,12 +74,10 @@ function buildProperties(fields: Field[], rowIndex: number): Record<string, stri
|
||||
const properties: Record<string, string> = {};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user