mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Transformations: Add Delimiter format option to Extract fields (#97340)
* Extract to fields: Add CSV extractor * Support escaping * Split at delimiter * Fix unused import * PR feedback * Rename extractor * Use number instead * update * Add comment about regex match * Add deimiter selector in editor * Default delimiter to comma --------- Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
This commit is contained in:
parent
930aa69192
commit
fde79020ef
@ -9,7 +9,7 @@ import {
|
|||||||
StandardEditorsRegistryItem,
|
StandardEditorsRegistryItem,
|
||||||
TransformerCategory,
|
TransformerCategory,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { InlineField, InlineFieldRow, Select, InlineSwitch, Input } from '@grafana/ui';
|
import { InlineField, InlineFieldRow, Select, InlineSwitch, Input, Combobox, ComboboxOption } from '@grafana/ui';
|
||||||
import { FieldNamePicker } from '@grafana/ui/src/components/MatchersUI/FieldNamePicker';
|
import { FieldNamePicker } from '@grafana/ui/src/components/MatchersUI/FieldNamePicker';
|
||||||
|
|
||||||
import { getTransformationContent } from '../docs/getTransformationContent';
|
import { getTransformationContent } from '../docs/getTransformationContent';
|
||||||
@ -31,7 +31,7 @@ const fieldNamePickerSettings: StandardEditorsRegistryItem<string, FieldNamePick
|
|||||||
|
|
||||||
export const extractFieldsTransformerEditor = ({
|
export const extractFieldsTransformerEditor = ({
|
||||||
input,
|
input,
|
||||||
options,
|
options = { delimiter: ',' },
|
||||||
onChange,
|
onChange,
|
||||||
}: TransformerUIProps<ExtractFieldsOptions>) => {
|
}: TransformerUIProps<ExtractFieldsOptions>) => {
|
||||||
const onPickSourceField = (source?: string) => {
|
const onPickSourceField = (source?: string) => {
|
||||||
@ -62,6 +62,13 @@ export const extractFieldsTransformerEditor = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onDelimiterChange = (val: ComboboxOption) => {
|
||||||
|
onChange({
|
||||||
|
...options,
|
||||||
|
delimiter: val.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const onToggleReplace = () => {
|
const onToggleReplace = () => {
|
||||||
if (options.replace) {
|
if (options.replace) {
|
||||||
options.keepTime = false;
|
options.keepTime = false;
|
||||||
@ -115,6 +122,19 @@ export const extractFieldsTransformerEditor = ({
|
|||||||
{options.format === FieldExtractorID.JSON && (
|
{options.format === FieldExtractorID.JSON && (
|
||||||
<JSONPathEditor options={options.jsonPaths ?? []} onChange={onJSONPathsChange} />
|
<JSONPathEditor options={options.jsonPaths ?? []} onChange={onJSONPathsChange} />
|
||||||
)}
|
)}
|
||||||
|
{options.format === FieldExtractorID.Delimiter && (
|
||||||
|
<InlineFieldRow>
|
||||||
|
<InlineField label="Delimiter" labelWidth={16}>
|
||||||
|
<Combobox
|
||||||
|
value={options.delimiter}
|
||||||
|
options={[{ value: ',' }, { value: ';' }, { value: '|' }]}
|
||||||
|
onChange={onDelimiterChange}
|
||||||
|
placeholder="Select delimiter..."
|
||||||
|
width={24}
|
||||||
|
/>
|
||||||
|
</InlineField>
|
||||||
|
</InlineFieldRow>
|
||||||
|
)}
|
||||||
<InlineFieldRow>
|
<InlineFieldRow>
|
||||||
<InlineField label={'Replace all fields'} labelWidth={16}>
|
<InlineField label={'Replace all fields'} labelWidth={16}>
|
||||||
<InlineSwitch value={options.replace ?? false} onChange={onToggleReplace} />
|
<InlineSwitch value={options.replace ?? false} onChange={onToggleReplace} />
|
||||||
|
@ -20,7 +20,9 @@ export const extractFieldsTransformer: SynchronousDataTransformerInfo<ExtractFie
|
|||||||
id: DataTransformerID.extractFields,
|
id: DataTransformerID.extractFields,
|
||||||
name: 'Extract fields',
|
name: 'Extract fields',
|
||||||
description: 'Parse fields from the contends of another',
|
description: 'Parse fields from the contends of another',
|
||||||
defaultOptions: {},
|
defaultOptions: {
|
||||||
|
delimiter: ',',
|
||||||
|
},
|
||||||
|
|
||||||
operator: (options, ctx) => (source) =>
|
operator: (options, ctx) => (source) =>
|
||||||
source.pipe(map((data) => extractFieldsTransformer.transformer(options, ctx)(data))),
|
source.pipe(map((data) => extractFieldsTransformer.transformer(options, ctx)(data))),
|
||||||
|
@ -151,4 +151,35 @@ describe('Extract fields from text', () => {
|
|||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Delimiter', () => {
|
||||||
|
it('splits by comma', async () => {
|
||||||
|
const extractor = fieldExtractors.get(FieldExtractorID.Delimiter);
|
||||||
|
const parse = extractor.getParser({});
|
||||||
|
const out = parse('a,b,c');
|
||||||
|
|
||||||
|
expect(out).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"a": 1,
|
||||||
|
"b": 1,
|
||||||
|
"c": 1,
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('trims whitespace', async () => {
|
||||||
|
const extractor = fieldExtractors.get(FieldExtractorID.Delimiter);
|
||||||
|
const parse = extractor.getParser({});
|
||||||
|
const out = parse(` a, b,c, d `);
|
||||||
|
|
||||||
|
expect(out).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"a": 1,
|
||||||
|
"b": 1,
|
||||||
|
"c": 1,
|
||||||
|
"d": 1,
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Registry, RegistryItem, stringStartsAsRegEx, stringToJsRegex } from '@grafana/data';
|
import { escapeStringForRegex, Registry, RegistryItem, stringStartsAsRegEx, stringToJsRegex } from '@grafana/data';
|
||||||
|
|
||||||
import { ExtractFieldsOptions, FieldExtractorID } from './types';
|
import { ExtractFieldsOptions, FieldExtractorID } from './types';
|
||||||
|
|
||||||
@ -133,7 +133,27 @@ const extLabels: FieldExtractor = {
|
|||||||
getParser: (options) => parseKeyValuePairs,
|
getParser: (options) => parseKeyValuePairs,
|
||||||
};
|
};
|
||||||
|
|
||||||
const fmts = [extJSON, extLabels, extRegExp];
|
const extDelimiter: FieldExtractor = {
|
||||||
|
id: FieldExtractorID.Delimiter,
|
||||||
|
name: 'Split by delimiter',
|
||||||
|
description: 'Splits at delimited values, such as commas',
|
||||||
|
getParser: ({ delimiter = ',' }) => {
|
||||||
|
// Match for delimiter with surrounding whitesapce (\s)
|
||||||
|
const splitRegExp = new RegExp(`\\s*${escapeStringForRegex(delimiter)}\\s*`, 'g');
|
||||||
|
|
||||||
|
return (raw: string) => {
|
||||||
|
// Try to split delimited values
|
||||||
|
const parts = raw.trim().split(splitRegExp);
|
||||||
|
const acc: Record<string, number> = {};
|
||||||
|
for (const part of parts) {
|
||||||
|
acc[part] = 1;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const fmts = [extJSON, extLabels, extDelimiter, extRegExp];
|
||||||
|
|
||||||
const extAuto: FieldExtractor = {
|
const extAuto: FieldExtractor = {
|
||||||
id: FieldExtractorID.Auto,
|
id: FieldExtractorID.Auto,
|
||||||
|
@ -3,6 +3,7 @@ export enum FieldExtractorID {
|
|||||||
KeyValues = 'kvp',
|
KeyValues = 'kvp',
|
||||||
Auto = 'auto',
|
Auto = 'auto',
|
||||||
RegExp = 'regexp',
|
RegExp = 'regexp',
|
||||||
|
Delimiter = 'delimiter',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface JSONPath {
|
export interface JSONPath {
|
||||||
@ -12,6 +13,7 @@ export interface JSONPath {
|
|||||||
export interface ExtractFieldsOptions {
|
export interface ExtractFieldsOptions {
|
||||||
source?: string;
|
source?: string;
|
||||||
jsonPaths?: JSONPath[];
|
jsonPaths?: JSONPath[];
|
||||||
|
delimiter?: string;
|
||||||
regExp?: string;
|
regExp?: string;
|
||||||
format?: FieldExtractorID;
|
format?: FieldExtractorID;
|
||||||
replace?: boolean;
|
replace?: boolean;
|
||||||
|
Loading…
Reference in New Issue
Block a user