mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
};
|
||||
@@ -37,4 +37,5 @@ export enum DataTransformerID {
|
||||
limit = 'limit',
|
||||
partitionByValues = 'partitionByValues',
|
||||
timeSeriesTable = 'timeSeriesTable',
|
||||
formatTime = 'formatTime',
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user