TimeSeries: fix & optimize time string parsing (#64640)

pull/64729/head
Leon Sorokin 2 years ago committed by GitHub
parent 8bb10f87e9
commit 855de98133
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      packages/grafana-data/src/field/fieldComparers.ts
  2. 32
      packages/grafana-data/src/transformations/transformers/convertFieldType.test.ts
  3. 4
      packages/grafana-data/src/transformations/transformers/convertFieldType.ts
  4. 12
      public/app/plugins/panel/timeseries/utils.ts

@ -18,6 +18,9 @@ export const fieldIndexComparer = (field: Field, reverse = false): IndexComparer
case FieldType.boolean:
return booleanIndexComparer(values, reverse);
case FieldType.time:
if (typeof field.values.get(0) === 'number') {
return timestampIndexComparer(values, reverse);
}
return timeIndexComparer(values, reverse);
default:
return naturalIndexComparer(reverse);
@ -76,6 +79,12 @@ const falsyComparer = (a: unknown, b: unknown): number => {
return 0;
};
const timestampIndexComparer = (values: Vector<number>, reverse: boolean): IndexComparer => {
let vals = values.toArray();
let mult = reverse ? -1 : 1;
return (a: number, b: number): number => mult * (vals[a] - vals[b]);
};
const timeIndexComparer = (values: Vector<unknown>, reverse: boolean): IndexComparer => {
return (a: number, b: number): number => {
const vA = values.get(a);

@ -391,12 +391,12 @@ describe('fieldToTimeField', () => {
// this needs to run in a non-UTC timezone env to ensure the parsing is not dependent on env tz settings
//process.env.TZ = 'Pacific/Easter';
it('should always parse ISO 8601 date strings in UTC timezone (e.g. 2011-10-05T14:48:00.000Z)', () => {
it('should properly parse ISO 8601 date strings in UTC offset timezone', () => {
const stringTimeField: Field = {
config: {},
name: 'ISO 8601 date strings',
type: FieldType.time,
values: new ArrayVector(['2021-11-11T19:45:00.000Z']),
values: new ArrayVector(['2021-11-11T19:45:00Z']),
};
expect(fieldToTimeField(stringTimeField)).toEqual({
@ -406,4 +406,32 @@ describe('fieldToTimeField', () => {
values: new ArrayVector([1636659900000]),
});
});
it('should properly parse additional ISO 8601 date strings with tz offsets and millis', () => {
const stringTimeField: Field = {
config: {},
name: 'ISO 8601 date strings',
type: FieldType.time,
values: new ArrayVector([
'2021-11-11T19:45:00+05:30',
'2021-11-11T19:45:00-05:30',
'2021-11-11T19:45:00+0530',
'2021-11-11T19:45:00-0530',
'2021-11-11T19:45:00.0000000000+05:30',
'2021-11-11T19:45:00.0000000000-0530',
'2021-11-11T19:45:00.000Z',
'2021-11-11T19:45:00.0000000000Z',
]),
};
expect(fieldToTimeField(stringTimeField)).toEqual({
config: {},
name: 'ISO 8601 date strings',
type: FieldType.time,
values: new ArrayVector([
1636640100000, 1636679700000, 1636640100000, 1636679700000, 1636640100000, 1636679700000, 1636659900000,
1636659900000,
]),
});
});
});

@ -109,8 +109,8 @@ export function convertFieldType(field: Field, opts: ConvertFieldTypeOptions): F
}
}
// matches ISO 8601, e.g. 2021-11-11T19:45:00.000Z (float portion optional)
const iso8601Regex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
// matches common ISO 8601 (see tests)
const iso8601Regex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3,})?(?:Z|[-+]\d{2}:?\d{2})$/;
/**
* @internal

@ -12,6 +12,7 @@ import {
SortedVector,
TimeRange,
} from '@grafana/data';
import { convertFieldType } from '@grafana/data/src/transformations/transformers/convertFieldType';
import { GraphFieldConfig, LineInterpolation } from '@grafana/schema';
import { applyNullInsertThreshold } from '@grafana/ui/src/components/GraphNG/nullInsertThreshold';
import { nullToValue } from '@grafana/ui/src/components/GraphNG/nullToValue';
@ -29,6 +30,17 @@ export function prepareGraphableFields(
return null;
}
// some datasources simply tag the field as time, but don't convert to milli epochs
// so we're stuck with doing the parsing here to avoid Moment slowness everywhere later
// this mutates (once)
for (let frame of series) {
for (let field of frame.fields) {
if (field.type === FieldType.time && typeof field.values.get(0) !== 'number') {
field.values = convertFieldType(field, { destinationType: FieldType.time }).values;
}
}
}
if (series.every((df) => df.meta?.type === DataFrameType.TimeSeriesLong)) {
series = prepareTimeSeriesLong(series);
}

Loading…
Cancel
Save