mirror of
https://github.com/grafana/grafana.git
synced 2024-11-25 18:30:41 -06:00
DataFrame: Handle nanosecond-precision timestamp fields (#64529)
handle nanosec-precision timestamps
This commit is contained in:
parent
45df536263
commit
c4c406aacd
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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<string[] | null>;
|
||||
|
||||
/**
|
||||
* 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<number[] | null>;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -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<number[] | null> = [];
|
||||
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,
|
||||
|
@ -137,6 +137,14 @@ export interface Field<T = any, V = Vector<T>> {
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user