From 114845a99a47ff7aa1593f69190d369150f9d70d Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Tue, 9 Jan 2024 08:24:16 -0800 Subject: [PATCH] Transformations: Use an explicit join seperator when converting from an array to string field (#80169) --- .../transformers/convertFieldType.test.ts | 32 ++++++++++++ .../transformers/convertFieldType.ts | 16 ++++-- .../transformations/transformers/reduce.ts | 3 +- .../ConvertFieldTypeTransformerEditor.tsx | 52 +++++++++++++------ 4 files changed, 84 insertions(+), 19 deletions(-) diff --git a/packages/grafana-data/src/transformations/transformers/convertFieldType.test.ts b/packages/grafana-data/src/transformations/transformers/convertFieldType.test.ts index 432ae01e9fe..15dfed729c8 100644 --- a/packages/grafana-data/src/transformations/transformers/convertFieldType.test.ts +++ b/packages/grafana-data/src/transformations/transformers/convertFieldType.test.ts @@ -359,6 +359,38 @@ describe('field convert types transformer', () => { ]); }); + it('will support custom join separators', () => { + const options = { + conversions: [{ targetField: 'vals', destinationType: FieldType.string, joinWith: '|' }], + }; + + const arrayValues = toDataFrame({ + fields: [ + { + name: 'vals', + type: FieldType.other, + values: [ + ['a', 'b', 2], + [3, 'x', 'y'], + ], + }, + ], + }); + + const stringified = convertFieldTypes(options, [arrayValues]); + expect( + stringified[0].fields.map((f) => ({ + type: f.type, + values: f.values, + })) + ).toEqual([ + { + type: FieldType.string, + values: ['a|b|2', '3|x|y'], + }, + ]); + }); + it('will convert time fields to strings', () => { const options = { conversions: [{ targetField: 'time', destinationType: FieldType.string, dateFormat: 'YYYY-MM' }], diff --git a/packages/grafana-data/src/transformations/transformers/convertFieldType.ts b/packages/grafana-data/src/transformations/transformers/convertFieldType.ts index b076ef4e6ee..669a9480bd1 100644 --- a/packages/grafana-data/src/transformations/transformers/convertFieldType.ts +++ b/packages/grafana-data/src/transformations/transformers/convertFieldType.ts @@ -27,6 +27,10 @@ export interface ConvertFieldTypeOptions { * Date format to parse a string datetime */ dateFormat?: string; + /** + * When converting an array to a string, the values can be joined with a custom separator + */ + joinWith?: string; /** * When converting a date to a string an option timezone. */ @@ -103,7 +107,7 @@ export function convertFieldType(field: Field, opts: ConvertFieldTypeOptions): F case FieldType.number: return fieldToNumberField(field); case FieldType.string: - return fieldToStringField(field, opts.dateFormat, { timeZone: opts.timezone }); + return fieldToStringField(field, opts.dateFormat, { timeZone: opts.timezone }, opts.joinWith); case FieldType.boolean: return fieldToBooleanField(field); case FieldType.enum: @@ -192,7 +196,8 @@ function fieldToBooleanField(field: Field): Field { export function fieldToStringField( field: Field, dateFormat?: string, - parseOptions?: DateTimeOptionsWhenParsing + parseOptions?: DateTimeOptionsWhenParsing, + joinWith?: string ): Field { let values = field.values; @@ -202,7 +207,12 @@ export function fieldToStringField( break; case FieldType.other: - values = values.map((v) => JSON.stringify(v)); + values = values.map((v) => { + if (joinWith?.length && Array.isArray(v)) { + return v.join(joinWith); + } + return JSON.stringify(v); // will quote strings and avoid "object" + }); break; default: diff --git a/packages/grafana-data/src/transformations/transformers/reduce.ts b/packages/grafana-data/src/transformations/transformers/reduce.ts index 89c684e051d..b0c862ee788 100644 --- a/packages/grafana-data/src/transformations/transformers/reduce.ts +++ b/packages/grafana-data/src/transformations/transformers/reduce.ts @@ -1,6 +1,6 @@ import { map } from 'rxjs/operators'; -import { guessFieldTypeForField } from '../../dataframe/processDataFrame'; +import { guessFieldTypeForField, guessFieldTypeFromValue } from '../../dataframe/processDataFrame'; import { getFieldDisplayName } from '../../field'; import { KeyValue } from '../../types/data'; import { DataFrame, Field, FieldType } from '../../types/dataFrame'; @@ -224,6 +224,7 @@ export function reduceFields(data: DataFrame[], matcher: FieldMatcher, reducerId const value = results[reducer]; const copy = { ...field, + type: guessFieldTypeFromValue(value), values: [value], }; copy.state = undefined; diff --git a/public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx b/public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx index f1957003191..ba6793ecd76 100644 --- a/public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx +++ b/public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx @@ -82,6 +82,18 @@ export const ConvertFieldTypeTransformerEditor = ({ [onChange, options] ); + const onJoinWithChange = useCallback( + (idx: number) => (e: ChangeEvent) => { + const conversions = options.conversions; + conversions[idx] = { ...conversions[idx], joinWith: e.currentTarget.value }; + onChange({ + ...options, + conversions: conversions, + }); + }, + [onChange, options] + ); + const onAddConvertFieldType = useCallback(() => { onChange({ ...options, @@ -119,6 +131,7 @@ export const ConvertFieldTypeTransformerEditor = ({ return ( <> {options.conversions.map((c: ConvertFieldTypeOptions, idx: number) => { + const targetField = findField(input?.[0], c.targetField); return (
@@ -152,22 +165,31 @@ export const ConvertFieldTypeTransformerEditor = ({ /> )} - {c.destinationType === FieldType.string && - (c.dateFormat || findField(input?.[0], c.targetField)?.type === FieldType.time) && ( - <> - - + {c.destinationType === FieldType.string && ( + <> + {(c.joinWith?.length || targetField?.type === FieldType.other) && ( + + - - + + +