diff --git a/packages/grafana-data/src/dataframe/DataFrameJSON.test.ts b/packages/grafana-data/src/dataframe/DataFrameJSON.test.ts index 6cd11677ba0..fe6930eaf2f 100644 --- a/packages/grafana-data/src/dataframe/DataFrameJSON.test.ts +++ b/packages/grafana-data/src/dataframe/DataFrameJSON.test.ts @@ -1,6 +1,7 @@ -import { FieldType } from '../types/dataFrame'; +import { ArrayVector } from '..'; +import { DataFrame, FieldType } from '../types/dataFrame'; -import { DataFrameJSON, dataFrameFromJSON } from './DataFrameJSON'; +import { DataFrameJSON, dataFrameFromJSON, dataFrameToJSON } from './DataFrameJSON'; describe('DataFrame JSON', () => { describe('when called with a DataFrame', () => { @@ -137,5 +138,104 @@ describe('DataFrame JSON', () => { } `); }); + + it('should decode fields with nanos', () => { + const json: DataFrameJSON = { + schema: { + fields: [ + { name: 'time1', type: FieldType.time }, + { name: 'time2', type: FieldType.time }, + ], + }, + data: { + values: [ + [1, 2, 3], + [4, 5, 6], + ], + nanos: [null, [7, 8, 9]], + }, + }; + + const frame = dataFrameFromJSON(json); + expect(frame).toMatchInlineSnapshot(` + { + "fields": [ + { + "config": {}, + "entities": {}, + "name": "time1", + "type": "time", + "values": [ + 1, + 2, + 3, + ], + }, + { + "config": {}, + "entities": {}, + "name": "time2", + "nanos": [ + 7, + 8, + 9, + ], + "type": "time", + "values": [ + 4, + 5, + 6, + ], + }, + ], + "length": 3, + } + `); + }); + + it('should encode fields with nanos', () => { + const inputFrame: DataFrame = { + refId: 'A', + meta: {}, + name: 'f1', + fields: [ + { + name: 'time1', + type: FieldType.time, + config: {}, + values: new ArrayVector([11, 12, 13]), + }, + { + name: 'time2', + type: FieldType.time, + config: {}, + values: new ArrayVector([14, 15, 16]), + nanos: [17, 18, 19], + }, + ], + length: 3, + }; + + const expectedJSON: DataFrameJSON = { + schema: { + fields: [ + { name: 'time1', type: FieldType.time, config: {} }, + { name: 'time2', type: FieldType.time, config: {} }, + ], + meta: {}, + name: 'f1', + refId: 'A', + }, + data: { + nanos: [null, [17, 18, 19]], + values: [ + [11, 12, 13], + [14, 15, 16], + ], + }, + }; + + expect(dataFrameToJSON(inputFrame)).toStrictEqual(expectedJSON); + }); }); }); diff --git a/packages/grafana-data/src/dataframe/DataFrameJSON.ts b/packages/grafana-data/src/dataframe/DataFrameJSON.ts index 79dd8e14acd..47b64fe72b6 100644 --- a/packages/grafana-data/src/dataframe/DataFrameJSON.ts +++ b/packages/grafana-data/src/dataframe/DataFrameJSON.ts @@ -1,4 +1,4 @@ -import { DataFrame, FieldType, FieldConfig, Labels, QueryResultMeta } from '../types'; +import { DataFrame, FieldType, FieldConfig, Labels, QueryResultMeta, Field } from '../types'; import { ArrayVector } from '../vector'; import { guessFieldTypeFromNameAndValue } from './processDataFrame'; @@ -56,6 +56,13 @@ export interface DataFrameData { * NOTE: currently only decoding is implemented */ enums?: Array; + + /** + * Holds integers between 0 and 999999, used by time-fields + * to store the nanosecond-precision that cannot be represented + * by the millisecond-based base value. + */ + nanos?: Array; } /** @@ -188,9 +195,11 @@ export function dataFrameFromJSON(dto: DataFrameJSON): DataFrame { type = FieldType.string; } + const nanos = data?.nanos?.[index]; + // TODO: expand arrays further using bases,factors - return { + const dataFrameField: Field & { entities: FieldValueEntityLookup } = { ...f, type: type ?? guessFieldType(f.name, buffer), config: f.config ?? {}, @@ -198,6 +207,12 @@ export function dataFrameFromJSON(dto: DataFrameJSON): DataFrame { // the presence of this prop is an optimization signal & lookup for consumers entities: entities ?? {}, }; + + if (nanos != null) { + dataFrameField.nanos = nanos; + } + + return dataFrameField; }); return { @@ -216,18 +231,34 @@ export function dataFrameToJSON(frame: DataFrame): DataFrameJSON { const data: DataFrameData = { values: [], }; + + const allNanos: Array = []; + let hasNanos = false; + const schema: DataFrameSchema = { refId: frame.refId, meta: frame.meta, name: frame.name, fields: frame.fields.map((f) => { - const { values, state, display, ...sfield } = f; + const { values, nanos, state, display, ...sfield } = f; delete (sfield as any).entities; data.values.push(values.toArray()); + + if (nanos != null) { + allNanos.push(nanos); + hasNanos = true; + } else { + allNanos.push(null); + } + return sfield; }), }; + if (hasNanos) { + data.nanos = allNanos; + } + return { schema, data, diff --git a/packages/grafana-data/src/types/dataFrame.ts b/packages/grafana-data/src/types/dataFrame.ts index 5ee8a3d30db..d7200416ce4 100644 --- a/packages/grafana-data/src/types/dataFrame.ts +++ b/packages/grafana-data/src/types/dataFrame.ts @@ -137,6 +137,14 @@ export interface Field> { */ config: FieldConfig; values: V; // The raw field values + + /** + * When type === FieldType.Time, this can optionally store + * the nanosecond-precison fractions as integers between + * 0 and 999999. + */ + nanos?: number[]; + labels?: Labels; /**