diff --git a/packages/grafana-data/src/transformations/transformers/convertFieldType.test.ts b/packages/grafana-data/src/transformations/transformers/convertFieldType.test.ts index a41e61f556f..b0e29ea9c8b 100644 --- a/packages/grafana-data/src/transformations/transformers/convertFieldType.test.ts +++ b/packages/grafana-data/src/transformations/transformers/convertFieldType.test.ts @@ -359,7 +359,7 @@ describe('field convert types transformer', () => { ]); }); - it('will support custom join separators', () => { + it('will support custom join separators for field type other', () => { const options = { conversions: [{ targetField: 'vals', destinationType: FieldType.string, joinWith: '|' }], }; @@ -391,6 +391,38 @@ describe('field convert types transformer', () => { ]); }); + it('will support custom join separators for field type string', () => { + const options = { + conversions: [{ targetField: 'string_arrays', destinationType: FieldType.string, joinWith: '&' }], + }; + + const stringArrayValues = toDataFrame({ + fields: [ + { + name: 'string_arrays', + type: FieldType.string, + values: [ + ['a', 'b', 'c'], + ['d', 'e', 'f'], + ], + }, + ], + }); + + const stringified = convertFieldTypes(options, [stringArrayValues]); + expect( + stringified[0].fields.map(({ type, values }) => ({ + type, + values, + })) + ).toEqual([ + { + type: FieldType.string, + values: ['a&b&c', 'd&e&f'], + }, + ]); + }); + 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 3b3967a68b5..4e90e67a84c 100644 --- a/packages/grafana-data/src/transformations/transformers/convertFieldType.ts +++ b/packages/grafana-data/src/transformations/transformers/convertFieldType.ts @@ -210,12 +210,15 @@ export function fieldToStringField( values = values.map((v) => dateTimeParse(v, parseOptions).format(dateFormat)); break; + // Handle both "string" and "other" types to ensure compatibility across Grafana versions (10 & 11) + // In some cases fields are classified as 'other' in Grafana 10 but as 'string' in Grafana 11 + case FieldType.string: case FieldType.other: values = values.map((v) => { if (joinWith?.length && Array.isArray(v)) { return v.join(joinWith); } - return JSON.stringify(v); // will quote strings and avoid "object" + return field.type === FieldType.other ? JSON.stringify(v) : `${v}`; }); break; diff --git a/public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx b/public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx index a40543eab4d..1416a906038 100644 --- a/public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx +++ b/public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx @@ -130,6 +130,14 @@ export const ConvertFieldTypeTransformerEditor = ({ <> {options.conversions.map((c: ConvertFieldTypeOptions, idx: number) => { const targetField = findField(input?.[0], c.targetField); + + // Show "Join with" input when: + // - A join value exists (maintains backward compatibility) + // - Target field type is 'other' (Grafana 10) or 'string' (Grafana 11) + // This ensures consistent UI across versions where arrays may be classified differently. + const shouldRenderJoinWith = + c.joinWith?.length || (targetField?.type && [FieldType.other, FieldType.string].includes(targetField.type)); + return (
@@ -169,7 +177,7 @@ export const ConvertFieldTypeTransformerEditor = ({ )} {c.destinationType === FieldType.string && ( <> - {(c.joinWith?.length || targetField?.type === FieldType.other) && ( + {shouldRenderJoinWith && ( )}