From 6314ce35eb6d8136c8e238abd2279cfa50ef3cc2 Mon Sep 17 00:00:00 2001 From: Ben Sully Date: Thu, 2 Dec 2021 09:49:18 +0000 Subject: [PATCH] Add second option to complex field conversion to increase flexibility This avoids falsely equating 'JSON' with FieldType.other, and instead allows multiple parsers to be used if the 'Complex' type is selected. Currently only JSON parsing is implemented, but others could be supported easily in future. --- .../transformers/convertFieldType.test.ts | 13 +++--- .../transformers/convertFieldType.ts | 45 ++++++++++++++++--- .../ConvertFieldTypeTransformerEditor.tsx | 38 +++++++++++++++- 3 files changed, 81 insertions(+), 15 deletions(-) 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 && ( + +