diff --git a/packages/grafana-data/src/transformations/transformers/convertFieldType.test.ts b/packages/grafana-data/src/transformations/transformers/convertFieldType.test.ts index f957732cd4b..fc3767bc262 100644 --- a/packages/grafana-data/src/transformations/transformers/convertFieldType.test.ts +++ b/packages/grafana-data/src/transformations/transformers/convertFieldType.test.ts @@ -8,6 +8,7 @@ import { convertFieldTypes, convertFieldTypeTransformer, fieldToTimeField, + ComplexFieldParserID, } from './convertFieldType'; describe('field convert type', () => { @@ -183,14 +184,14 @@ describe('field convert types transformer', () => { ]); }); - it('will convert field to complex objects', () => { + it('will convert JSON fields to complex objects', () => { const options = { conversions: [ - { targetField: 'numbers', destinationType: FieldType.other }, - { targetField: 'objects', destinationType: FieldType.other }, - { targetField: 'arrays', destinationType: FieldType.other }, - { targetField: 'invalids', destinationType: FieldType.other }, - { targetField: 'mixed', destinationType: FieldType.other }, + { targetField: 'numbers', destinationType: FieldType.other, inputFormat: ComplexFieldParserID.JSON }, + { targetField: 'objects', destinationType: FieldType.other, inputFormat: ComplexFieldParserID.JSON }, + { targetField: 'arrays', destinationType: FieldType.other, inputFormat: ComplexFieldParserID.JSON }, + { targetField: 'invalids', destinationType: FieldType.other, inputFormat: ComplexFieldParserID.JSON }, + { targetField: 'mixed', destinationType: FieldType.other, inputFormat: ComplexFieldParserID.JSON }, ], }; diff --git a/packages/grafana-data/src/transformations/transformers/convertFieldType.ts b/packages/grafana-data/src/transformations/transformers/convertFieldType.ts index 967bdccaa38..abafe30ebe9 100644 --- a/packages/grafana-data/src/transformations/transformers/convertFieldType.ts +++ b/packages/grafana-data/src/transformations/transformers/convertFieldType.ts @@ -7,6 +7,7 @@ import { dateTimeParse } from '../../datetime'; import { ArrayVector } from '../../vector'; import { fieldMatchers } from '../matchers'; import { FieldMatcherID } from '../matchers/ids'; +import { Registry, RegistryItem } from '../../utils/Registry'; export interface ConvertFieldTypeTransformerOptions { conversions: ConvertFieldTypeOptions[]; @@ -25,6 +26,10 @@ export interface ConvertFieldTypeOptions { * Date format to parse a string datetime */ dateFormat?: string; + /** + * Format to parse a complex field as + */ + inputFormat?: ComplexFieldParserID; } export const convertFieldTypeTransformer: SynchronousDataTransformerInfo = { @@ -100,7 +105,8 @@ export function convertFieldType(field: Field, opts: ConvertFieldTypeOptions): F case FieldType.boolean: return fieldToBooleanField(field); case FieldType.other: - return fieldToComplexField(field); + const parser = complexFieldParsers.get(opts.inputFormat ?? ComplexFieldParserID.JSON); + return fieldToComplexField(field, parser); default: return field; } @@ -180,15 +186,11 @@ function fieldToStringField(field: Field): Field { }; } -function fieldToComplexField(field: Field): Field { +function fieldToComplexField(field: Field, parser: ComplexFieldParser): Field { const complexValues = field.values.toArray().slice(); for (let s = 0; s < complexValues.length; s++) { - try { - complexValues[s] = JSON.parse(complexValues[s]); - } catch { - complexValues[s] = null; - } + complexValues[s] = parser.parse(complexValues[s]); } return { @@ -219,3 +221,32 @@ export function ensureTimeField(field: Field, dateFormat?: string): Field { } return fieldToTimeField(field, dateFormat); } + +/// Complex field parsing. Currently only JSON is supported, but more formats can be added +/// by extending the ComplexFieldParserID enum and ComplexFieldParser interface. + +export enum ComplexFieldParserID { + JSON = 'json', +} + +export interface ComplexFieldParser extends RegistryItem { + parse: (v: any) => any | null; +} + +const jsonFieldParser: ComplexFieldParser = { + id: ComplexFieldParserID.JSON, + name: 'JSON', + description: 'Parse field as JSON', + parse: (v: any) => { + try { + return JSON.parse(v); + } catch { + return null; + } + }, +}; + +/** + * Registry of complex field parsers, used with the ConvertFieldTypeTransformer. + */ +export const complexFieldParsers = new Registry(() => [jsonFieldParser]); diff --git a/public/app/core/components/TransformersUI/ConvertFieldTypeTransformerEditor.tsx b/public/app/core/components/TransformersUI/ConvertFieldTypeTransformerEditor.tsx index 456761e5c5c..86fe09864d4 100644 --- a/public/app/core/components/TransformersUI/ConvertFieldTypeTransformerEditor.tsx +++ b/public/app/core/components/TransformersUI/ConvertFieldTypeTransformerEditor.tsx @@ -10,7 +10,11 @@ import { TransformerUIProps, } from '@grafana/data'; -import { ConvertFieldTypeTransformerOptions } from '@grafana/data/src/transformations/transformers/convertFieldType'; +import { + ComplexFieldParserID, + complexFieldParsers, + ConvertFieldTypeTransformerOptions, +} from '@grafana/data/src/transformations/transformers/convertFieldType'; import { Button, InlineField, InlineFieldRow, Input, Select } from '@grafana/ui'; import { FieldNamePicker } from '../../../../../packages/grafana-ui/src/components/MatchersUI/FieldNamePicker'; import { ConvertFieldTypeOptions } from '../../../../../packages/grafana-data/src/transformations/transformers/convertFieldType'; @@ -29,9 +33,11 @@ export const ConvertFieldTypeTransformerEditor: React.FC (value: string | undefined) => { const conversions = options.conversions; @@ -68,6 +74,18 @@ export const ConvertFieldTypeTransformerEditor: React.FC (value: SelectableValue) => { + const conversions = options.conversions; + conversions[idx] = { ...conversions[idx], inputFormat: value.value }; + onChange({ + ...options, + conversions: conversions, + }); + }, + [onChange, options] + ); + const onAddConvertFieldType = useCallback(() => { onChange({ ...options, @@ -121,6 +139,22 @@ export const ConvertFieldTypeTransformerEditor: React.FC )} + {c.destinationType === FieldType.other && ( + +