Transformations: Use an explicit join seperator when converting from an array to string field (#80169)

This commit is contained in:
Ryan McKinley 2024-01-09 08:24:16 -08:00 committed by GitHub
parent 3c045d1dfb
commit 114845a99a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 84 additions and 19 deletions

View File

@ -359,6 +359,38 @@ describe('field convert types transformer', () => {
]);
});
it('will support custom join separators', () => {
const options = {
conversions: [{ targetField: 'vals', destinationType: FieldType.string, joinWith: '|' }],
};
const arrayValues = toDataFrame({
fields: [
{
name: 'vals',
type: FieldType.other,
values: [
['a', 'b', 2],
[3, 'x', 'y'],
],
},
],
});
const stringified = convertFieldTypes(options, [arrayValues]);
expect(
stringified[0].fields.map((f) => ({
type: f.type,
values: f.values,
}))
).toEqual([
{
type: FieldType.string,
values: ['a|b|2', '3|x|y'],
},
]);
});
it('will convert time fields to strings', () => {
const options = {
conversions: [{ targetField: 'time', destinationType: FieldType.string, dateFormat: 'YYYY-MM' }],

View File

@ -27,6 +27,10 @@ export interface ConvertFieldTypeOptions {
* Date format to parse a string datetime
*/
dateFormat?: string;
/**
* When converting an array to a string, the values can be joined with a custom separator
*/
joinWith?: string;
/**
* When converting a date to a string an option timezone.
*/
@ -103,7 +107,7 @@ export function convertFieldType(field: Field, opts: ConvertFieldTypeOptions): F
case FieldType.number:
return fieldToNumberField(field);
case FieldType.string:
return fieldToStringField(field, opts.dateFormat, { timeZone: opts.timezone });
return fieldToStringField(field, opts.dateFormat, { timeZone: opts.timezone }, opts.joinWith);
case FieldType.boolean:
return fieldToBooleanField(field);
case FieldType.enum:
@ -192,7 +196,8 @@ function fieldToBooleanField(field: Field): Field {
export function fieldToStringField(
field: Field,
dateFormat?: string,
parseOptions?: DateTimeOptionsWhenParsing
parseOptions?: DateTimeOptionsWhenParsing,
joinWith?: string
): Field {
let values = field.values;
@ -202,7 +207,12 @@ export function fieldToStringField(
break;
case FieldType.other:
values = values.map((v) => JSON.stringify(v));
values = values.map((v) => {
if (joinWith?.length && Array.isArray(v)) {
return v.join(joinWith);
}
return JSON.stringify(v); // will quote strings and avoid "object"
});
break;
default:

View File

@ -1,6 +1,6 @@
import { map } from 'rxjs/operators';
import { guessFieldTypeForField } from '../../dataframe/processDataFrame';
import { guessFieldTypeForField, guessFieldTypeFromValue } from '../../dataframe/processDataFrame';
import { getFieldDisplayName } from '../../field';
import { KeyValue } from '../../types/data';
import { DataFrame, Field, FieldType } from '../../types/dataFrame';
@ -224,6 +224,7 @@ export function reduceFields(data: DataFrame[], matcher: FieldMatcher, reducerId
const value = results[reducer];
const copy = {
...field,
type: guessFieldTypeFromValue(value),
values: [value],
};
copy.state = undefined;

View File

@ -82,6 +82,18 @@ export const ConvertFieldTypeTransformerEditor = ({
[onChange, options]
);
const onJoinWithChange = useCallback(
(idx: number) => (e: ChangeEvent<HTMLInputElement>) => {
const conversions = options.conversions;
conversions[idx] = { ...conversions[idx], joinWith: e.currentTarget.value };
onChange({
...options,
conversions: conversions,
});
},
[onChange, options]
);
const onAddConvertFieldType = useCallback(() => {
onChange({
...options,
@ -119,6 +131,7 @@ export const ConvertFieldTypeTransformerEditor = ({
return (
<>
{options.conversions.map((c: ConvertFieldTypeOptions, idx: number) => {
const targetField = findField(input?.[0], c.targetField);
return (
<div key={`${c.targetField}-${idx}`}>
<InlineFieldRow>
@ -152,8 +165,15 @@ export const ConvertFieldTypeTransformerEditor = ({
/>
</InlineField>
)}
{c.destinationType === FieldType.string &&
(c.dateFormat || findField(input?.[0], c.targetField)?.type === FieldType.time) && (
{c.destinationType === FieldType.string && (
<>
{(c.joinWith?.length || targetField?.type === FieldType.other) && (
<InlineField label="Join with" tooltip="Use an explicit separator when joining array values">
<Input value={c.joinWith} placeholder={'JSON'} onChange={onJoinWithChange(idx)} width={9} />
</InlineField>
)}
{c.dateFormat ||
(targetField?.type === FieldType.time && (
<>
<InlineField label="Date format" tooltip="Specify the output format.">
<Input
@ -167,6 +187,8 @@ export const ConvertFieldTypeTransformerEditor = ({
<Select options={timeZoneOptions} value={c.timezone} onChange={onTzChange(idx)} isClearable />
</InlineField>
</>
))}
</>
)}
<Button
size="md"