Transforms: Add Format Time Transform (Alpha) (#72319)

* Stub transform editor

* Mostly working

* Get things working 💪

* Add tests

* Add alpha flag

* Timezone support

* Remove debug statement

* Fix tests

* Prettier fix

* Fix linter error

* One more linter fix
This commit is contained in:
Kyle Cunningham
2023-07-26 17:08:36 -05:00
committed by GitHub
parent 18a364eb2f
commit 3dc60cd2d7
6 changed files with 270 additions and 0 deletions

View File

@@ -6,6 +6,7 @@ import { filterFieldsTransformer, filterFramesTransformer } from './transformers
import { filterFieldsByNameTransformer } from './transformers/filterByName';
import { filterFramesByRefIdTransformer } from './transformers/filterByRefId';
import { filterByValueTransformer } from './transformers/filterByValue';
import { formatTimeTransformer } from './transformers/formatTime';
import { groupByTransformer } from './transformers/groupBy';
import { groupingToMatrixTransformer } from './transformers/groupingToMatrix';
import { histogramTransformer } from './transformers/histogram';
@@ -29,6 +30,7 @@ export const standardTransformers = {
filterFramesTransformer,
filterFramesByRefIdTransformer,
filterByValueTransformer,
formatTimeTransformer,
orderFieldsTransformer,
organizeFieldsTransformer,
reduceTransformer,

View File

@@ -0,0 +1,89 @@
import { toDataFrame } from '../../dataframe/processDataFrame';
import { FieldType } from '../../types/dataFrame';
import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry';
import { createTimeFormatter, formatTimeTransformer } from './formatTime';
describe('Format Time Transformer', () => {
beforeAll(() => {
mockTransformationsRegistry([formatTimeTransformer]);
});
it('will convert time to formatted string', () => {
const options = {
timeField: 'time',
outputFormat: 'YYYY-MM',
useTimezone: false,
};
const formatter = createTimeFormatter(options.timeField, options.outputFormat, options.useTimezone);
const frame = toDataFrame({
fields: [
{
name: 'time',
type: FieldType.time,
values: [1612939600000, 1689192000000, 1682025600000, 1690328089000, 1691011200000],
},
],
});
const newFrame = formatter(frame.fields);
expect(newFrame[0].values).toEqual(['2021-02', '2023-07', '2023-04', '2023-07', '2023-08']);
});
it('will handle formats with times', () => {
const options = {
timeField: 'time',
outputFormat: 'YYYY-MM h:mm:ss a',
useTimezone: false,
};
const formatter = createTimeFormatter(options.timeField, options.outputFormat, options.useTimezone);
const frame = toDataFrame({
fields: [
{
name: 'time',
type: FieldType.time,
values: [1612939600000, 1689192000000, 1682025600000, 1690328089000, 1691011200000],
},
],
});
const newFrame = formatter(frame.fields);
expect(newFrame[0].values).toEqual([
'2021-02 1:46:40 am',
'2023-07 2:00:00 pm',
'2023-04 3:20:00 pm',
'2023-07 5:34:49 pm',
'2023-08 3:20:00 pm',
]);
});
it('will handle null times', () => {
const options = {
timeField: 'time',
outputFormat: 'YYYY-MM h:mm:ss a',
useTimezone: false,
};
const formatter = createTimeFormatter(options.timeField, options.outputFormat, options.useTimezone);
const frame = toDataFrame({
fields: [
{
name: 'time',
type: FieldType.time,
values: [1612939600000, 1689192000000, 1682025600000, 1690328089000, null],
},
],
});
const newFrame = formatter(frame.fields);
expect(newFrame[0].values).toEqual([
'2021-02 1:46:40 am',
'2023-07 2:00:00 pm',
'2023-04 3:20:00 pm',
'2023-07 5:34:49 pm',
'Invalid date',
]);
});
});

View File

@@ -0,0 +1,76 @@
import moment from 'moment-timezone';
import { map } from 'rxjs/operators';
import { getTimeZone, getTimeZoneInfo } from '../../datetime';
import { Field, FieldType } from '../../types';
import { DataTransformerInfo } from '../../types/transformations';
import { DataTransformerID } from './ids';
export interface FormatTimeTransformerOptions {
timeField: string;
outputFormat: string;
useTimezone: boolean;
}
export const formatTimeTransformer: DataTransformerInfo<FormatTimeTransformerOptions> = {
id: DataTransformerID.formatTime,
name: 'Format Time',
description: 'Set the output format of a time field',
defaultOptions: { timeField: '', outputFormat: '', useTimezone: true },
operator: (options) => (source) =>
source.pipe(
map((data) => {
// If a field and a format are configured
// then format the time output
const formatter = createTimeFormatter(options.timeField, options.outputFormat, options.useTimezone);
if (!Array.isArray(data) || data.length === 0) {
return data;
}
return data.map((frame) => ({
...frame,
fields: formatter(frame.fields),
}));
})
),
};
/**
* @internal
*/
export const createTimeFormatter =
(timeField: string, outputFormat: string, useTimezone: boolean) => (fields: Field[]) => {
const tz = getTimeZone();
return fields.map((field) => {
// Find the configured field
if (field.name === timeField) {
// Update values to use the configured format
const newVals = field.values.map((value) => {
const date = moment(value);
// Apply configured timezone if the
// option has been set. Otherwise
// use the date directly
if (useTimezone) {
const info = getTimeZoneInfo(tz, value);
const realTz = info !== undefined ? info.ianaName : 'UTC';
return date.tz(realTz).format(outputFormat);
} else {
return date.format(outputFormat);
}
});
return {
...field,
type: FieldType.string,
values: newVals,
};
}
return field;
});
};

View File

@@ -37,4 +37,5 @@ export enum DataTransformerID {
limit = 'limit',
partitionByValues = 'partitionByValues',
timeSeriesTable = 'timeSeriesTable',
formatTime = 'formatTime',
}