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.
This commit is contained in:
Ben Sully 2021-12-02 09:49:18 +00:00
parent 30de927764
commit 6314ce35eb
No known key found for this signature in database
GPG Key ID: 7E8D2C2A7B2E2B1A
3 changed files with 81 additions and 15 deletions

View File

@ -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 },
],
};

View File

@ -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<ConvertFieldTypeTransformerOptions> = {
@ -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<ComplexFieldParser>(() => [jsonFieldParser]);

View File

@ -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<TransformerUIProps<Conv
{ value: FieldType.string, label: 'String' },
{ value: FieldType.time, label: 'Time' },
{ value: FieldType.boolean, label: 'Boolean' },
{ value: FieldType.other, label: 'JSON' },
{ value: FieldType.other, label: 'Complex' },
];
const complexFormat = complexFieldParsers.selectOptions();
const onSelectField = useCallback(
(idx) => (value: string | undefined) => {
const conversions = options.conversions;
@ -68,6 +74,18 @@ export const ConvertFieldTypeTransformerEditor: React.FC<TransformerUIProps<Conv
[onChange, options]
);
const onComplexInputFormat = useCallback(
(idx) => (value: SelectableValue<ComplexFieldParserID>) => {
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<TransformerUIProps<Conv
<Input value={c.dateFormat} placeholder={'e.g. YYYY-MM-DD'} onChange={onInputFormat(idx)} width={24} />
</InlineField>
)}
{c.destinationType === FieldType.other && (
<InlineField
label="Input format"
tooltip="Specify the format of the input field so Grafana can parse the field correctly."
>
<Select
menuShouldPortal
options={complexFormat.options as Array<SelectableValue<ComplexFieldParserID>>}
value={c.inputFormat}
defaultValue={complexFormat.options[0]}
placeholder={'JSON'}
onChange={onComplexInputFormat(idx)}
width={24}
/>
</InlineField>
)}
<Button
size="md"
icon="trash-alt"