mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
DataFrame: split DataFrameHelper into MutableDataFrame and FieldCache (#18795)
* add appending utility * add appending utility * update comment * rename to mutable * move mutable functions out of DataFrameHelper * move mutable functions out of DataFrameHelper * move mutable functions out of DataFrameHelper * turn DataFrameHelper into FieldCache * guess time from name * graph the numbers * return the timeField, not just the index * just warn on duplicate field names * only use a parser if the input is a string * append init all fields to the same length * typo * only parse string if value is a string * DataFrame: test fixes * Switch to null for missing values * Fixed tests
This commit is contained in:
committed by
Torkel Ödegaard
parent
13f55bc5e8
commit
c777301535
@@ -63,11 +63,11 @@ export interface Vector<T = any> {
|
|||||||
toJSON(): any; // same results as toArray()
|
toJSON(): any; // same results as toArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Field<T = any> {
|
export interface Field<T = any, V = Vector<T>> {
|
||||||
name: string; // The column name
|
name: string; // The column name
|
||||||
type: FieldType;
|
type: FieldType;
|
||||||
config: FieldConfig;
|
config: FieldConfig;
|
||||||
values: Vector<T>; // `buffer` when JSON
|
values: V; // The raw field values
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache of reduced values
|
* Cache of reduced values
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ Object {
|
|||||||
"values": Array [
|
"values": Array [
|
||||||
null,
|
null,
|
||||||
4,
|
4,
|
||||||
NaN,
|
undefined,
|
||||||
NaN,
|
NaN,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import isNumber from 'lodash/isNumber';
|
|||||||
// Types
|
// Types
|
||||||
import { DataFrame, Field, FieldType, FieldConfig } from '../types';
|
import { DataFrame, Field, FieldType, FieldConfig } from '../types';
|
||||||
import { guessFieldTypeFromValue } from './processDataFrame';
|
import { guessFieldTypeFromValue } from './processDataFrame';
|
||||||
import { DataFrameHelper } from './dataFrameHelper';
|
import { MutableDataFrame } from './dataFrameHelper';
|
||||||
|
|
||||||
export enum CSVHeaderStyle {
|
export enum CSVHeaderStyle {
|
||||||
full,
|
full,
|
||||||
@@ -55,8 +55,8 @@ export class CSVReader {
|
|||||||
callback?: CSVParseCallbacks;
|
callback?: CSVParseCallbacks;
|
||||||
|
|
||||||
state: ParseState;
|
state: ParseState;
|
||||||
data: DataFrameHelper[];
|
data: MutableDataFrame[];
|
||||||
current: DataFrameHelper;
|
current: MutableDataFrame;
|
||||||
|
|
||||||
constructor(options?: CSVOptions) {
|
constructor(options?: CSVOptions) {
|
||||||
if (!options) {
|
if (!options) {
|
||||||
@@ -65,7 +65,7 @@ export class CSVReader {
|
|||||||
this.config = options.config || {};
|
this.config = options.config || {};
|
||||||
this.callback = options.callback;
|
this.callback = options.callback;
|
||||||
|
|
||||||
this.current = new DataFrameHelper({ fields: [] });
|
this.current = new MutableDataFrame({ fields: [] });
|
||||||
this.state = ParseState.Starting;
|
this.state = ParseState.Starting;
|
||||||
this.data = [];
|
this.data = [];
|
||||||
}
|
}
|
||||||
@@ -97,7 +97,7 @@ export class CSVReader {
|
|||||||
if (isName || headerKeys.hasOwnProperty(k)) {
|
if (isName || headerKeys.hasOwnProperty(k)) {
|
||||||
// Starting a new table after reading rows
|
// Starting a new table after reading rows
|
||||||
if (this.state === ParseState.ReadingRows) {
|
if (this.state === ParseState.ReadingRows) {
|
||||||
this.current = new DataFrameHelper({ fields: [] });
|
this.current = new MutableDataFrame({ fields: [] });
|
||||||
this.data.push(this.current);
|
this.data.push(this.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,8 +171,8 @@ export class CSVReader {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
readCSV(text: string): DataFrameHelper[] {
|
readCSV(text: string): MutableDataFrame[] {
|
||||||
this.current = new DataFrameHelper({ fields: [] });
|
this.current = new MutableDataFrame({ fields: [] });
|
||||||
this.data = [this.current];
|
this.data = [this.current];
|
||||||
|
|
||||||
const papacfg = {
|
const papacfg = {
|
||||||
|
|||||||
@@ -1,20 +1,17 @@
|
|||||||
import { DataFrameDTO, FieldDTO, FieldType } from '../types';
|
import { DataFrameDTO, FieldType } from '../types';
|
||||||
import { DataFrameHelper } from './dataFrameHelper';
|
import { FieldCache, MutableDataFrame } from './dataFrameHelper';
|
||||||
|
import { toDataFrame } from './processDataFrame';
|
||||||
|
|
||||||
describe('dataFrameHelper', () => {
|
describe('dataFrameHelper', () => {
|
||||||
const frame: DataFrameDTO = {
|
const frame = toDataFrame({
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'time', type: FieldType.time, values: [100, 200, 300] },
|
{ name: 'time', type: FieldType.time, values: [100, 200, 300] },
|
||||||
{ name: 'name', type: FieldType.string, values: ['a', 'b', 'c'] },
|
{ name: 'name', type: FieldType.string, values: ['a', 'b', 'c'] },
|
||||||
{ name: 'value', type: FieldType.number, values: [1, 2, 3] },
|
{ name: 'value', type: FieldType.number, values: [1, 2, 3] },
|
||||||
{ name: 'value', type: FieldType.number, values: [4, 5, 6] },
|
{ name: 'value', type: FieldType.number, values: [4, 5, 6] },
|
||||||
],
|
],
|
||||||
};
|
|
||||||
const ext = new DataFrameHelper(frame);
|
|
||||||
|
|
||||||
it('Should get a valid count for the fields', () => {
|
|
||||||
expect(ext.length).toEqual(3);
|
|
||||||
});
|
});
|
||||||
|
const ext = new FieldCache(frame);
|
||||||
|
|
||||||
it('Should get the first field with a duplicate name', () => {
|
it('Should get the first field with a duplicate name', () => {
|
||||||
const field = ext.getFieldByName('value');
|
const field = ext.getFieldByName('value');
|
||||||
@@ -25,15 +22,17 @@ describe('dataFrameHelper', () => {
|
|||||||
|
|
||||||
describe('FieldCache', () => {
|
describe('FieldCache', () => {
|
||||||
it('when creating a new FieldCache from fields should be able to query cache', () => {
|
it('when creating a new FieldCache from fields should be able to query cache', () => {
|
||||||
const fields: FieldDTO[] = [
|
const frame = toDataFrame({
|
||||||
{ name: 'time', type: FieldType.time },
|
fields: [
|
||||||
{ name: 'string', type: FieldType.string },
|
{ name: 'time', type: FieldType.time },
|
||||||
{ name: 'number', type: FieldType.number },
|
{ name: 'string', type: FieldType.string },
|
||||||
{ name: 'boolean', type: FieldType.boolean },
|
{ name: 'number', type: FieldType.number },
|
||||||
{ name: 'other', type: FieldType.other },
|
{ name: 'boolean', type: FieldType.boolean },
|
||||||
{ name: 'undefined' },
|
{ name: 'other', type: FieldType.other },
|
||||||
];
|
{ name: 'undefined' },
|
||||||
const fieldCache = new DataFrameHelper({ fields });
|
],
|
||||||
|
});
|
||||||
|
const fieldCache = new FieldCache(frame);
|
||||||
const allFields = fieldCache.getFields();
|
const allFields = fieldCache.getFields();
|
||||||
expect(allFields).toHaveLength(6);
|
expect(allFields).toHaveLength(6);
|
||||||
|
|
||||||
@@ -99,17 +98,55 @@ describe('reverse', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const helper = new DataFrameHelper(frame);
|
const helper = new MutableDataFrame(frame);
|
||||||
|
|
||||||
expect(helper.getFieldByName('time')!.values.toArray()).toEqual([100, 200, 300]);
|
expect(helper.values.time.toArray()).toEqual([100, 200, 300]);
|
||||||
expect(helper.getFieldByName('name')!.values.toArray()).toEqual(['a', 'b', 'c']);
|
expect(helper.values.name.toArray()).toEqual(['a', 'b', 'c']);
|
||||||
expect(helper.getFieldByName('value')!.values.toArray()).toEqual([1, 2, 3]);
|
expect(helper.values.value.toArray()).toEqual([1, 2, 3]);
|
||||||
|
|
||||||
helper.reverse();
|
helper.reverse();
|
||||||
|
|
||||||
expect(helper.getFieldByName('time')!.values.toArray()).toEqual([300, 200, 100]);
|
expect(helper.values.time.toArray()).toEqual([300, 200, 100]);
|
||||||
expect(helper.getFieldByName('name')!.values.toArray()).toEqual(['c', 'b', 'a']);
|
expect(helper.values.name.toArray()).toEqual(['c', 'b', 'a']);
|
||||||
expect(helper.getFieldByName('value')!.values.toArray()).toEqual([3, 2, 1]);
|
expect(helper.values.value.toArray()).toEqual([3, 2, 1]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Apending DataFrame', () => {
|
||||||
|
it('Should append values', () => {
|
||||||
|
const dto: DataFrameDTO = {
|
||||||
|
fields: [
|
||||||
|
{ name: 'time', type: FieldType.time, values: [100] },
|
||||||
|
{ name: 'name', type: FieldType.string, values: ['a', 'b'] },
|
||||||
|
{ name: 'value', type: FieldType.number, values: [1, 2, 3] },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const frame = new MutableDataFrame(dto);
|
||||||
|
expect(frame.values.time.toArray()).toEqual([100, null, null]);
|
||||||
|
|
||||||
|
// Set a value on the second row
|
||||||
|
frame.set(1, { time: 200, name: 'BB', value: 20 });
|
||||||
|
expect(frame.toArray()).toEqual([
|
||||||
|
{ time: 100, name: 'a', value: 1 }, // 1
|
||||||
|
{ time: 200, name: 'BB', value: 20 }, // 2
|
||||||
|
{ time: null, name: null, value: 3 }, // 3
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Set a value on the second row
|
||||||
|
frame.add({ value2: 'XXX' }, true);
|
||||||
|
expect(frame.toArray()).toEqual([
|
||||||
|
{ time: 100, name: 'a', value: 1, value2: null }, // 1
|
||||||
|
{ time: 200, name: 'BB', value: 20, value2: null }, // 2
|
||||||
|
{ time: null, name: null, value: 3, value2: null }, // 3
|
||||||
|
{ time: null, name: null, value: null, value2: 'XXX' }, // 4
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Make sure length survives a spread operator
|
||||||
|
const keys = Object.keys(frame);
|
||||||
|
const copy = { ...frame } as any;
|
||||||
|
expect(keys).toContain('length');
|
||||||
|
expect(copy.length).toEqual(frame.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,174 +1,43 @@
|
|||||||
import { Field, FieldType, DataFrame, Vector, FieldDTO, DataFrameDTO } from '../types/dataFrame';
|
import { Field, FieldType, DataFrame, Vector, FieldDTO, DataFrameDTO } from '../types/dataFrame';
|
||||||
import { Labels, QueryResultMeta } from '../types/data';
|
import { Labels, QueryResultMeta, KeyValue } from '../types/data';
|
||||||
import { guessFieldTypeForField, guessFieldTypeFromValue } from './processDataFrame';
|
import { guessFieldTypeForField, guessFieldTypeFromValue, toDataFrameDTO } from './processDataFrame';
|
||||||
import { ArrayVector } from './vector';
|
import { ArrayVector, MutableVector, vectorToArray, CircularVector } from './vector';
|
||||||
import isArray from 'lodash/isArray';
|
import isArray from 'lodash/isArray';
|
||||||
|
import isString from 'lodash/isString';
|
||||||
|
|
||||||
export class DataFrameHelper implements DataFrame {
|
export class FieldCache {
|
||||||
refId?: string;
|
fields: Field[] = [];
|
||||||
meta?: QueryResultMeta;
|
|
||||||
name?: string;
|
|
||||||
fields: Field[];
|
|
||||||
labels?: Labels;
|
|
||||||
length = 0; // updated so it is the length of all fields
|
|
||||||
|
|
||||||
private fieldByName: { [key: string]: Field } = {};
|
private fieldByName: { [key: string]: Field } = {};
|
||||||
private fieldByType: { [key: string]: Field[] } = {};
|
private fieldByType: { [key: string]: Field[] } = {};
|
||||||
|
|
||||||
constructor(data?: DataFrame | DataFrameDTO) {
|
constructor(private data: DataFrame) {
|
||||||
if (!data) {
|
this.fields = data.fields;
|
||||||
data = { fields: [] }; //
|
|
||||||
}
|
|
||||||
this.refId = data.refId;
|
|
||||||
this.meta = data.meta;
|
|
||||||
this.name = data.name;
|
|
||||||
this.labels = data.labels;
|
|
||||||
this.fields = [];
|
|
||||||
for (let i = 0; i < data.fields.length; i++) {
|
|
||||||
this.addField(data.fields[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addFieldFor(value: any, name?: string): Field {
|
for (const field of data.fields) {
|
||||||
if (!name) {
|
// Make sure it has a type
|
||||||
name = `Field ${this.fields.length + 1}`;
|
if (field.type === FieldType.other) {
|
||||||
}
|
const t = guessFieldTypeForField(field);
|
||||||
return this.addField({
|
if (t) {
|
||||||
name,
|
field.type = t;
|
||||||
type: guessFieldTypeFromValue(value),
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the direction of all fields
|
|
||||||
*/
|
|
||||||
reverse() {
|
|
||||||
for (const f of this.fields) {
|
|
||||||
f.values.toArray().reverse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateTypeIndex(field: Field) {
|
|
||||||
// Make sure it has a type
|
|
||||||
if (field.type === FieldType.other) {
|
|
||||||
const t = guessFieldTypeForField(field);
|
|
||||||
if (t) {
|
|
||||||
field.type = t;
|
|
||||||
}
|
}
|
||||||
}
|
if (!this.fieldByType[field.type]) {
|
||||||
if (!this.fieldByType[field.type]) {
|
this.fieldByType[field.type] = [];
|
||||||
this.fieldByType[field.type] = [];
|
}
|
||||||
}
|
this.fieldByType[field.type].push(field);
|
||||||
this.fieldByType[field.type].push(field);
|
|
||||||
}
|
|
||||||
|
|
||||||
addField(f: Field | FieldDTO): Field {
|
if (this.fieldByName[field.name]) {
|
||||||
const type = f.type || FieldType.other;
|
console.warn('Duplicate field names in DataFrame: ', field.name);
|
||||||
const values =
|
|
||||||
!f.values || isArray(f.values)
|
|
||||||
? new ArrayVector(f.values as any[] | undefined) // array or empty
|
|
||||||
: (f.values as Vector);
|
|
||||||
|
|
||||||
// And a name
|
|
||||||
let name = f.name;
|
|
||||||
if (!name) {
|
|
||||||
if (type === FieldType.time) {
|
|
||||||
name = `Time ${this.fields.length + 1}`;
|
|
||||||
} else {
|
} else {
|
||||||
name = `Column ${this.fields.length + 1}`;
|
this.fieldByName[field.name] = field;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const field: Field = {
|
|
||||||
name,
|
|
||||||
type,
|
|
||||||
config: f.config || {},
|
|
||||||
values,
|
|
||||||
};
|
|
||||||
this.updateTypeIndex(field);
|
|
||||||
|
|
||||||
if (this.fieldByName[field.name]) {
|
|
||||||
console.warn('Duplicate field names in DataFrame: ', field.name);
|
|
||||||
} else {
|
|
||||||
this.fieldByName[field.name] = field;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the lengths all match
|
|
||||||
if (field.values.length !== this.length) {
|
|
||||||
if (field.values.length > this.length) {
|
|
||||||
// Add `null` to all other values
|
|
||||||
const newlen = field.values.length;
|
|
||||||
for (const fx of this.fields) {
|
|
||||||
const arr = fx.values as ArrayVector;
|
|
||||||
while (fx.values.length !== newlen) {
|
|
||||||
arr.buffer.push(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.length = field.values.length;
|
|
||||||
} else {
|
|
||||||
const arr = field.values as ArrayVector;
|
|
||||||
while (field.values.length !== this.length) {
|
|
||||||
arr.buffer.push(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.fields.push(field);
|
|
||||||
return field;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This will add each value to the corresponding column
|
|
||||||
*/
|
|
||||||
appendRow(row: any[]) {
|
|
||||||
for (let i = this.fields.length; i < row.length; i++) {
|
|
||||||
this.addFieldFor(row[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The first line may change the field types
|
|
||||||
if (this.length < 1) {
|
|
||||||
this.fieldByType = {};
|
|
||||||
for (let i = 0; i < this.fields.length; i++) {
|
|
||||||
const f = this.fields[i];
|
|
||||||
if (!f.type || f.type === FieldType.other) {
|
|
||||||
f.type = guessFieldTypeFromValue(row[i]);
|
|
||||||
}
|
|
||||||
this.updateTypeIndex(f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < this.fields.length; i++) {
|
|
||||||
const f = this.fields[i];
|
|
||||||
let v = row[i];
|
|
||||||
if (!f.parse) {
|
|
||||||
f.parse = makeFieldParser(v, f);
|
|
||||||
}
|
|
||||||
v = f.parse(v);
|
|
||||||
|
|
||||||
const arr = f.values as ArrayVector;
|
|
||||||
arr.buffer.push(v); // may be undefined
|
|
||||||
}
|
|
||||||
this.length++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add any values that match the field names
|
|
||||||
*/
|
|
||||||
appendRowFrom(obj: { [key: string]: any }) {
|
|
||||||
for (const f of this.fields) {
|
|
||||||
const v = obj[f.name];
|
|
||||||
if (!f.parse) {
|
|
||||||
f.parse = makeFieldParser(v, f);
|
|
||||||
}
|
|
||||||
|
|
||||||
const arr = f.values as ArrayVector;
|
|
||||||
arr.buffer.push(f.parse(v)); // may be undefined
|
|
||||||
}
|
|
||||||
this.length++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getFields(type?: FieldType): Field[] {
|
getFields(type?: FieldType): Field[] {
|
||||||
if (!type) {
|
if (!type) {
|
||||||
return [...this.fields]; // All fields
|
return [...this.data.fields]; // All fields
|
||||||
}
|
}
|
||||||
const fields = this.fieldByType[type];
|
const fields = this.fieldByType[type];
|
||||||
if (fields) {
|
if (fields) {
|
||||||
@@ -202,7 +71,7 @@ export class DataFrameHelper implements DataFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeFieldParser(value: string, field: Field): (value: string) => any {
|
function makeFieldParser(value: any, field: Field): (value: string) => any {
|
||||||
if (!field.type) {
|
if (!field.type) {
|
||||||
if (field.name === 'time' || field.name === 'Time') {
|
if (field.name === 'time' || field.name === 'Time') {
|
||||||
field.type = FieldType.time;
|
field.type = FieldType.time;
|
||||||
@@ -227,3 +96,297 @@ function makeFieldParser(value: string, field: Field): (value: string) => any {
|
|||||||
// Just pass the string back
|
// Just pass the string back
|
||||||
return (value: string) => value;
|
return (value: string) => value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MutableField<T = any> = Field<T, MutableVector<T>>;
|
||||||
|
|
||||||
|
type MutableVectorCreator = (buffer?: any[]) => MutableVector;
|
||||||
|
|
||||||
|
export const MISSING_VALUE: any = null;
|
||||||
|
|
||||||
|
export class MutableDataFrame<T = any> implements DataFrame, MutableVector<T> {
|
||||||
|
name?: string;
|
||||||
|
labels?: Labels;
|
||||||
|
refId?: string;
|
||||||
|
meta?: QueryResultMeta;
|
||||||
|
|
||||||
|
fields: MutableField[] = [];
|
||||||
|
values: KeyValue<MutableVector> = {};
|
||||||
|
|
||||||
|
private first: Vector = new ArrayVector();
|
||||||
|
private creator: MutableVectorCreator;
|
||||||
|
|
||||||
|
constructor(source?: DataFrame | DataFrameDTO, creator?: MutableVectorCreator) {
|
||||||
|
// This creates the underlying storage buffers
|
||||||
|
this.creator = creator
|
||||||
|
? creator
|
||||||
|
: (buffer?: any[]) => {
|
||||||
|
return new ArrayVector(buffer);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Copy values from
|
||||||
|
if (source) {
|
||||||
|
const { name, labels, refId, meta, fields } = source;
|
||||||
|
if (name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
if (labels) {
|
||||||
|
this.labels = labels;
|
||||||
|
}
|
||||||
|
if (refId) {
|
||||||
|
this.refId = refId;
|
||||||
|
}
|
||||||
|
if (meta) {
|
||||||
|
this.meta = meta;
|
||||||
|
}
|
||||||
|
if (fields) {
|
||||||
|
for (const f of fields) {
|
||||||
|
this.addField(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Length to show up if you use spread
|
||||||
|
Object.defineProperty(this, 'length', {
|
||||||
|
enumerable: true,
|
||||||
|
get: () => {
|
||||||
|
return this.first.length;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defined for Vector interface
|
||||||
|
get length() {
|
||||||
|
return this.first.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
addFieldFor(value: any, name?: string): MutableField {
|
||||||
|
return this.addField({
|
||||||
|
name: name || '', // Will be filled in
|
||||||
|
type: guessFieldTypeFromValue(value),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addField(f: Field | FieldDTO, startLength?: number): MutableField {
|
||||||
|
let buffer: any[] | undefined = undefined;
|
||||||
|
|
||||||
|
if (f.values) {
|
||||||
|
if (isArray(f.values)) {
|
||||||
|
buffer = f.values as any[];
|
||||||
|
} else {
|
||||||
|
buffer = (f.values as Vector).toArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let type = f.type;
|
||||||
|
|
||||||
|
if (!type && ('time' === f.name || 'Time' === f.name)) {
|
||||||
|
type = FieldType.time;
|
||||||
|
} else {
|
||||||
|
if (!type && buffer && buffer.length) {
|
||||||
|
type = guessFieldTypeFromValue(buffer[0]);
|
||||||
|
}
|
||||||
|
if (!type) {
|
||||||
|
type = FieldType.other;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure it has a name
|
||||||
|
let name = f.name;
|
||||||
|
if (!name) {
|
||||||
|
if (type === FieldType.time) {
|
||||||
|
name = this.values['Time'] ? `Time ${this.fields.length + 1}` : 'Time';
|
||||||
|
} else {
|
||||||
|
name = `Field ${this.fields.length + 1}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const field: MutableField = {
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
config: f.config || {},
|
||||||
|
values: this.creator(buffer),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (type === FieldType.other) {
|
||||||
|
type = guessFieldTypeForField(field);
|
||||||
|
if (type) {
|
||||||
|
field.type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fields.push(field);
|
||||||
|
this.first = this.fields[0].values;
|
||||||
|
|
||||||
|
// The Field Already exists
|
||||||
|
if (this.values[name]) {
|
||||||
|
console.warn(`Duplicate field names found: ${name}, only the first will be accessible`);
|
||||||
|
} else {
|
||||||
|
this.values[name] = field.values;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the field starts with a given length
|
||||||
|
if (startLength) {
|
||||||
|
while (field.values.length < startLength) {
|
||||||
|
field.values.add(MISSING_VALUE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
validate() {
|
||||||
|
// Make sure all arrays are the same length
|
||||||
|
const length = this.fields.reduce((v: number, f) => {
|
||||||
|
return Math.max(v, f.values.length);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
// Add empty elements until everything mastches
|
||||||
|
for (const field of this.fields) {
|
||||||
|
while (field.values.length !== length) {
|
||||||
|
field.values.add(MISSING_VALUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private addMissingFieldsFor(value: any) {
|
||||||
|
for (const key of Object.keys(value)) {
|
||||||
|
if (!this.values[key]) {
|
||||||
|
this.addField({
|
||||||
|
name: key,
|
||||||
|
type: guessFieldTypeFromValue(value[key]),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse all values
|
||||||
|
*/
|
||||||
|
reverse() {
|
||||||
|
for (const f of this.fields) {
|
||||||
|
f.values.reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will add each value to the corresponding column
|
||||||
|
*/
|
||||||
|
appendRow(row: any[]) {
|
||||||
|
// Add any extra columns
|
||||||
|
for (let i = this.fields.length; i < row.length; i++) {
|
||||||
|
this.addField({
|
||||||
|
name: `Field ${i + 1}`,
|
||||||
|
type: guessFieldTypeFromValue(row[i]),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first line may change the field types
|
||||||
|
if (this.length < 1) {
|
||||||
|
for (let i = 0; i < this.fields.length; i++) {
|
||||||
|
const f = this.fields[i];
|
||||||
|
if (!f.type || f.type === FieldType.other) {
|
||||||
|
f.type = guessFieldTypeFromValue(row[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < this.fields.length; i++) {
|
||||||
|
const f = this.fields[i];
|
||||||
|
let v = row[i];
|
||||||
|
if (f.type !== FieldType.string && isString(v)) {
|
||||||
|
if (!f.parse) {
|
||||||
|
f.parse = makeFieldParser(v, f);
|
||||||
|
}
|
||||||
|
v = f.parse(v);
|
||||||
|
}
|
||||||
|
f.values.add(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add all properties of the value as fields on the frame
|
||||||
|
*/
|
||||||
|
add(value: T, addMissingFields?: boolean) {
|
||||||
|
if (addMissingFields) {
|
||||||
|
this.addMissingFieldsFor(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Will add one value for every field
|
||||||
|
const obj = value as any;
|
||||||
|
for (const field of this.fields) {
|
||||||
|
let val = obj[field.name];
|
||||||
|
|
||||||
|
if (field.type !== FieldType.string && isString(val)) {
|
||||||
|
if (!field.parse) {
|
||||||
|
field.parse = makeFieldParser(val, field);
|
||||||
|
}
|
||||||
|
val = field.parse(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val === undefined) {
|
||||||
|
val = MISSING_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
field.values.add(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set(index: number, value: T, addMissingFields?: boolean) {
|
||||||
|
if (index > this.length) {
|
||||||
|
throw new Error('Unable ot set value beyond current length');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addMissingFields) {
|
||||||
|
this.addMissingFieldsFor(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const obj = (value as any) || {};
|
||||||
|
for (const field of this.fields) {
|
||||||
|
field.values.set(index, obj[field.name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an object with a property for each field in the DataFrame
|
||||||
|
*/
|
||||||
|
get(idx: number): T {
|
||||||
|
const v: any = {};
|
||||||
|
for (const field of this.fields) {
|
||||||
|
v[field.name] = field.values.get(idx);
|
||||||
|
}
|
||||||
|
return v as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
toArray(): T[] {
|
||||||
|
return vectorToArray(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The simplified JSON values used in JSON.stringify()
|
||||||
|
*/
|
||||||
|
toJSON() {
|
||||||
|
return toDataFrameDTO(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CircularOptions {
|
||||||
|
append?: 'head' | 'tail';
|
||||||
|
capacity?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This dataframe can have values constantly added, and will never
|
||||||
|
* exceed the given capacity
|
||||||
|
*/
|
||||||
|
export class CircularDataFrame<T = any> extends MutableDataFrame<T> {
|
||||||
|
constructor(options: CircularOptions) {
|
||||||
|
super(undefined, (buffer?: any[]) => {
|
||||||
|
return new CircularVector({
|
||||||
|
buffer,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { FieldType, DataFrameDTO } from '../types/index';
|
import { FieldType, DataFrameDTO } from '../types/index';
|
||||||
import { DataFrameHelper } from './dataFrameHelper';
|
import { MutableDataFrame } from './dataFrameHelper';
|
||||||
import { DataFrameView } from './dataFrameView';
|
import { DataFrameView } from './dataFrameView';
|
||||||
import { DateTime } from './moment_wrapper';
|
import { DateTime } from './moment_wrapper';
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ describe('dataFrameView', () => {
|
|||||||
{ name: 'value', type: FieldType.number, values: [1, 2, 3] },
|
{ name: 'value', type: FieldType.number, values: [1, 2, 3] },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
const ext = new DataFrameHelper(frame);
|
const ext = new MutableDataFrame(frame);
|
||||||
const vector = new DataFrameView<MySpecialObject>(ext);
|
const vector = new DataFrameView<MySpecialObject>(ext);
|
||||||
|
|
||||||
it('Should get a typed vector', () => {
|
it('Should get a typed vector', () => {
|
||||||
@@ -73,4 +73,12 @@ describe('dataFrameView', () => {
|
|||||||
expect(first.name).toEqual('b');
|
expect(first.name).toEqual('b');
|
||||||
expect(first.name).toEqual(second.name);
|
expect(first.name).toEqual(second.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('toJSON returns plain object', () => {
|
||||||
|
expect(vector.toJSON()[0]).toEqual({
|
||||||
|
time: 100,
|
||||||
|
name: 'a',
|
||||||
|
value: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { fieldReducers, ReducerID, reduceField } from './fieldReducer';
|
|||||||
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { Field, FieldType } from '../types/index';
|
import { Field, FieldType } from '../types/index';
|
||||||
import { DataFrameHelper } from './dataFrameHelper';
|
import { MutableDataFrame } from './dataFrameHelper';
|
||||||
import { ArrayVector } from './vector';
|
import { ArrayVector } from './vector';
|
||||||
import { guessFieldTypeFromValue } from './processDataFrame';
|
import { guessFieldTypeFromValue } from './processDataFrame';
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ function createField<T>(name: string, values?: T[], type?: FieldType): Field<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('Stats Calculators', () => {
|
describe('Stats Calculators', () => {
|
||||||
const basicTable = new DataFrameHelper({
|
const basicTable = new MutableDataFrame({
|
||||||
fields: [{ name: 'a', values: [10, 20] }, { name: 'b', values: [20, 30] }, { name: 'c', values: [30, 40] }],
|
fields: [{ name: 'a', values: [10, 20] }, { name: 'b', values: [20, 30] }, { name: 'c', values: [30, 40] }],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
} from './processDataFrame';
|
} from './processDataFrame';
|
||||||
import { FieldType, TimeSeries, TableData, DataFrameDTO } from '../types/index';
|
import { FieldType, TimeSeries, TableData, DataFrameDTO } from '../types/index';
|
||||||
import { dateTime } from './moment_wrapper';
|
import { dateTime } from './moment_wrapper';
|
||||||
import { DataFrameHelper } from './dataFrameHelper';
|
import { MutableDataFrame } from './dataFrameHelper';
|
||||||
|
|
||||||
describe('toDataFrame', () => {
|
describe('toDataFrame', () => {
|
||||||
it('converts timeseries to series', () => {
|
it('converts timeseries to series', () => {
|
||||||
@@ -89,7 +89,7 @@ describe('toDataFrame', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Guess Colum Types from series', () => {
|
it('Guess Colum Types from series', () => {
|
||||||
const series = new DataFrameHelper({
|
const series = new MutableDataFrame({
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'A (number)', values: [123, null] },
|
{ name: 'A (number)', values: [123, null] },
|
||||||
{ name: 'B (strings)', values: [null, 'Hello'] },
|
{ name: 'B (strings)', values: [null, 'Hello'] },
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
} from '../types/index';
|
} from '../types/index';
|
||||||
import { isDateTime } from './moment_wrapper';
|
import { isDateTime } from './moment_wrapper';
|
||||||
import { ArrayVector, SortedVector } from './vector';
|
import { ArrayVector, SortedVector } from './vector';
|
||||||
import { DataFrameHelper } from './dataFrameHelper';
|
import { MutableDataFrame } from './dataFrameHelper';
|
||||||
import { deprecationWarning } from './deprecationWarning';
|
import { deprecationWarning } from './deprecationWarning';
|
||||||
|
|
||||||
function convertTableToDataFrame(table: TableData): DataFrame {
|
function convertTableToDataFrame(table: TableData): DataFrame {
|
||||||
@@ -221,7 +221,7 @@ export const toDataFrame = (data: any): DataFrame => {
|
|||||||
if (data.hasOwnProperty('fields')) {
|
if (data.hasOwnProperty('fields')) {
|
||||||
// @deprecated -- remove in 6.5
|
// @deprecated -- remove in 6.5
|
||||||
if (data.hasOwnProperty('rows')) {
|
if (data.hasOwnProperty('rows')) {
|
||||||
const v = new DataFrameHelper(data as DataFrameDTO);
|
const v = new MutableDataFrame(data as DataFrameDTO);
|
||||||
const rows = data.rows as any[][];
|
const rows = data.rows as any[][];
|
||||||
for (let i = 0; i < rows.length; i++) {
|
for (let i = 0; i < rows.length; i++) {
|
||||||
v.appendRow(rows[i]);
|
v.appendRow(rows[i]);
|
||||||
@@ -234,7 +234,9 @@ export const toDataFrame = (data: any): DataFrame => {
|
|||||||
if (data.hasOwnProperty('length')) {
|
if (data.hasOwnProperty('length')) {
|
||||||
return data as DataFrame;
|
return data as DataFrame;
|
||||||
}
|
}
|
||||||
return new DataFrameHelper(data as DataFrameDTO);
|
|
||||||
|
// This will convert the array values into Vectors
|
||||||
|
return new MutableDataFrame(data as DataFrameDTO);
|
||||||
}
|
}
|
||||||
if (data.hasOwnProperty('datapoints')) {
|
if (data.hasOwnProperty('datapoints')) {
|
||||||
return convertTimeSeriesToDataFrame(data);
|
return convertTimeSeriesToDataFrame(data);
|
||||||
@@ -344,6 +346,35 @@ export function sortDataFrame(data: DataFrame, sortIndex?: number, reverse = fal
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy with all values reversed
|
||||||
|
*/
|
||||||
|
export function reverseDataFrame(data: DataFrame): DataFrame {
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
fields: data.fields.map(f => {
|
||||||
|
const copy = [...f.values.toArray()];
|
||||||
|
copy.reverse();
|
||||||
|
return {
|
||||||
|
...f,
|
||||||
|
values: new ArrayVector(copy),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getTimeField = (series: DataFrame): { timeField?: Field; timeIndex?: number } => {
|
||||||
|
for (let i = 0; i < series.fields.length; i++) {
|
||||||
|
if (series.fields[i].type === FieldType.time) {
|
||||||
|
return {
|
||||||
|
timeField: series.fields[i],
|
||||||
|
timeIndex: i,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper to get an array from each field value
|
* Wrapper to get an array from each field value
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ describe('Append Transformer', () => {
|
|||||||
const fieldC = processed.fields[2];
|
const fieldC = processed.fields[2];
|
||||||
|
|
||||||
expect(fieldA.values.toArray()).toEqual([1, 2, 3, 4]);
|
expect(fieldA.values.toArray()).toEqual([1, 2, 3, 4]);
|
||||||
expect(fieldB.values.toArray()).toEqual([100, 200, undefined, undefined]);
|
expect(fieldB.values.toArray()).toEqual([100, 200, null, null]);
|
||||||
expect(fieldC.values.toArray()).toEqual([undefined, undefined, 3000, 4000]);
|
expect(fieldC.values.toArray()).toEqual([null, null, 3000, 4000]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { DataTransformerInfo } from './transformers';
|
import { DataTransformerInfo } from './transformers';
|
||||||
import { DataFrame } from '../../types/dataFrame';
|
import { DataFrame } from '../../types/dataFrame';
|
||||||
import { DataTransformerID } from './ids';
|
import { DataTransformerID } from './ids';
|
||||||
import { DataFrameHelper } from '../dataFrameHelper';
|
import { MutableDataFrame } from '../dataFrameHelper';
|
||||||
import { KeyValue } from '../../types/data';
|
|
||||||
import { AppendedVectors } from '../vector';
|
|
||||||
|
|
||||||
export interface AppendOptions {}
|
export interface AppendOptions {}
|
||||||
|
|
||||||
@@ -23,34 +21,37 @@ export const appendTransformer: DataTransformerInfo<AppendOptions> = {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
let length = 0;
|
// Add the first row
|
||||||
const processed = new DataFrameHelper();
|
const processed = new MutableDataFrame();
|
||||||
for (let i = 0; i < data.length; i++) {
|
for (const f of data[0].fields) {
|
||||||
|
processed.addField({
|
||||||
|
...f,
|
||||||
|
values: [...f.values.toArray()],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i < data.length; i++) {
|
||||||
const frame = data[i];
|
const frame = data[i];
|
||||||
const used: KeyValue<boolean> = {};
|
const startLength = frame.length;
|
||||||
for (let j = 0; j < frame.fields.length; j++) {
|
for (let j = 0; j < frame.fields.length; j++) {
|
||||||
const src = frame.fields[j];
|
const src = frame.fields[j];
|
||||||
if (used[src.name]) {
|
let vals = processed.values[src.name];
|
||||||
continue;
|
if (!vals) {
|
||||||
|
vals = processed.addField(
|
||||||
|
{
|
||||||
|
...src,
|
||||||
|
values: [],
|
||||||
|
},
|
||||||
|
startLength
|
||||||
|
).values;
|
||||||
}
|
}
|
||||||
used[src.name] = true;
|
|
||||||
|
|
||||||
let f = processed.getFieldByName(src.name);
|
// Add each row
|
||||||
if (!f) {
|
for (let k = 0; k < frame.length; k++) {
|
||||||
f = processed.addField({
|
vals.add(src.values.get(k));
|
||||||
...src,
|
|
||||||
values: new AppendedVectors(length),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
(f.values as AppendedVectors).append(src.values);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure all fields have their length updated
|
|
||||||
length += frame.length;
|
|
||||||
processed.length = length;
|
|
||||||
for (const f of processed.fields) {
|
|
||||||
(f.values as AppendedVectors).setLength(processed.length);
|
|
||||||
}
|
}
|
||||||
|
processed.validate();
|
||||||
}
|
}
|
||||||
return [processed];
|
return [processed];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,29 @@ export function vectorToArray<T>(v: Vector<T>): T[] {
|
|||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ArrayVector<T = any> implements Vector<T> {
|
/**
|
||||||
|
* Apache arrow vectors are Read/Write
|
||||||
|
*/
|
||||||
|
export interface ReadWriteVector<T = any> extends Vector<T> {
|
||||||
|
set: (index: number, value: T) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vector with standard manipulation functions
|
||||||
|
*/
|
||||||
|
export interface MutableVector<T = any> extends ReadWriteVector<T> {
|
||||||
|
/**
|
||||||
|
* Adds the value to the vector
|
||||||
|
*/
|
||||||
|
add: (value: T) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* modifies the vector so it is now the oposite order
|
||||||
|
*/
|
||||||
|
reverse: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ArrayVector<T = any> implements MutableVector<T> {
|
||||||
buffer: T[];
|
buffer: T[];
|
||||||
|
|
||||||
constructor(buffer?: T[]) {
|
constructor(buffer?: T[]) {
|
||||||
@@ -19,10 +41,22 @@ export class ArrayVector<T = any> implements Vector<T> {
|
|||||||
return this.buffer.length;
|
return this.buffer.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add(value: T) {
|
||||||
|
this.buffer.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
get(index: number): T {
|
get(index: number): T {
|
||||||
return this.buffer[index];
|
return this.buffer[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set(index: number, value: T) {
|
||||||
|
this.buffer[index] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
reverse() {
|
||||||
|
this.buffer.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
toArray(): T[] {
|
toArray(): T[] {
|
||||||
return this.buffer;
|
return this.buffer;
|
||||||
}
|
}
|
||||||
@@ -109,7 +143,7 @@ interface CircularOptions<T> {
|
|||||||
* This supports addting to the 'head' or 'tail' and will grow the buffer
|
* This supports addting to the 'head' or 'tail' and will grow the buffer
|
||||||
* to match a configured capacity.
|
* to match a configured capacity.
|
||||||
*/
|
*/
|
||||||
export class CircularVector<T = any> implements Vector<T> {
|
export class CircularVector<T = any> implements MutableVector<T> {
|
||||||
private buffer: T[];
|
private buffer: T[];
|
||||||
private index: number;
|
private index: number;
|
||||||
private capacity: number;
|
private capacity: number;
|
||||||
@@ -202,6 +236,10 @@ export class CircularVector<T = any> implements Vector<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reverse() {
|
||||||
|
this.buffer.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the value to the buffer
|
* Add the value to the buffer
|
||||||
*/
|
*/
|
||||||
@@ -211,6 +249,10 @@ export class CircularVector<T = any> implements Vector<T> {
|
|||||||
return this.buffer[(index + this.index) % this.buffer.length];
|
return this.buffer[(index + this.index) % this.buffer.length];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set(index: number, value: T) {
|
||||||
|
this.buffer[(index + this.index) % this.buffer.length] = value;
|
||||||
|
}
|
||||||
|
|
||||||
get length() {
|
get length() {
|
||||||
return this.buffer.length;
|
return this.buffer.length;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DataFrameHelper, FieldType, LogRowModel } from '@grafana/data';
|
import { FieldType, LogRowModel, MutableDataFrame } from '@grafana/data';
|
||||||
import { getRowContexts } from './LogRowContextProvider';
|
import { getRowContexts } from './LogRowContextProvider';
|
||||||
import { Labels, LogLevel } from '@grafana/data/src';
|
import { Labels, LogLevel } from '@grafana/data/src';
|
||||||
import { DataQueryResponse } from '../../types';
|
import { DataQueryResponse } from '../../types';
|
||||||
@@ -6,7 +6,7 @@ import { DataQueryResponse } from '../../types';
|
|||||||
describe('getRowContexts', () => {
|
describe('getRowContexts', () => {
|
||||||
describe('when called with a DataFrame and results are returned', () => {
|
describe('when called with a DataFrame and results are returned', () => {
|
||||||
it('then the result should be in correct format', async () => {
|
it('then the result should be in correct format', async () => {
|
||||||
const firstResult = new DataFrameHelper({
|
const firstResult = new MutableDataFrame({
|
||||||
refId: 'B',
|
refId: 'B',
|
||||||
labels: {},
|
labels: {},
|
||||||
fields: [
|
fields: [
|
||||||
@@ -14,7 +14,7 @@ describe('getRowContexts', () => {
|
|||||||
{ name: 'line', type: FieldType.string, values: ['3', '2', '1'] },
|
{ name: 'line', type: FieldType.string, values: ['3', '2', '1'] },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const secondResult = new DataFrameHelper({
|
const secondResult = new MutableDataFrame({
|
||||||
refId: 'B',
|
refId: 'B',
|
||||||
labels: {},
|
labels: {},
|
||||||
fields: [
|
fields: [
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { getFieldProperties, getFieldDisplayValues, GetFieldDisplayValuesOptions } from './fieldDisplay';
|
import { getFieldProperties, getFieldDisplayValues, GetFieldDisplayValuesOptions } from './fieldDisplay';
|
||||||
import { ReducerID, Threshold, DataFrameHelper } from '@grafana/data';
|
import { ReducerID, Threshold, toDataFrame } from '@grafana/data';
|
||||||
import { GrafanaThemeType } from '../types/theme';
|
import { GrafanaThemeType } from '../types/theme';
|
||||||
import { getTheme } from '../themes/index';
|
import { getTheme } from '../themes/index';
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ describe('FieldDisplay', () => {
|
|||||||
// Simple test dataset
|
// Simple test dataset
|
||||||
const options: GetFieldDisplayValuesOptions = {
|
const options: GetFieldDisplayValuesOptions = {
|
||||||
data: [
|
data: [
|
||||||
new DataFrameHelper({
|
toDataFrame({
|
||||||
name: 'Series Name',
|
name: 'Series Name',
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'Field 1', values: ['a', 'b', 'c'] },
|
{ name: 'Field 1', values: ['a', 'b', 'c'] },
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
DisplayValue,
|
DisplayValue,
|
||||||
GraphSeriesValue,
|
GraphSeriesValue,
|
||||||
DataFrameView,
|
DataFrameView,
|
||||||
|
getTimeField,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
|
|
||||||
import toNumber from 'lodash/toNumber';
|
import toNumber from 'lodash/toNumber';
|
||||||
@@ -82,17 +83,6 @@ export interface GetFieldDisplayValuesOptions {
|
|||||||
|
|
||||||
export const DEFAULT_FIELD_DISPLAY_VALUES_LIMIT = 25;
|
export const DEFAULT_FIELD_DISPLAY_VALUES_LIMIT = 25;
|
||||||
|
|
||||||
const getTimeColumnIdx = (series: DataFrame) => {
|
|
||||||
let timeColumn = -1;
|
|
||||||
for (let i = 0; i < series.fields.length; i++) {
|
|
||||||
if (series.fields[i].type === FieldType.time) {
|
|
||||||
timeColumn = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return timeColumn;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): FieldDisplay[] => {
|
export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): FieldDisplay[] => {
|
||||||
const { data, replaceVariables, fieldOptions } = options;
|
const { data, replaceVariables, fieldOptions } = options;
|
||||||
const { defaults, override } = fieldOptions;
|
const { defaults, override } = fieldOptions;
|
||||||
@@ -117,7 +107,7 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
|
|||||||
|
|
||||||
scopedVars[DataLinkBuiltInVars.seriesName] = { text: 'Series', value: series.name };
|
scopedVars[DataLinkBuiltInVars.seriesName] = { text: 'Series', value: series.name };
|
||||||
|
|
||||||
const timeColumn = getTimeColumnIdx(series);
|
const { timeField } = getTimeField(series);
|
||||||
const view = new DataFrameView(series);
|
const view = new DataFrameView(series);
|
||||||
|
|
||||||
for (let i = 0; i < series.fields.length && !hitLimit; i++) {
|
for (let i = 0; i < series.fields.length && !hitLimit; i++) {
|
||||||
@@ -184,9 +174,9 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
|
|||||||
let sparkline: GraphSeriesValue[][] | undefined = undefined;
|
let sparkline: GraphSeriesValue[][] | undefined = undefined;
|
||||||
|
|
||||||
// Single sparkline for every reducer
|
// Single sparkline for every reducer
|
||||||
if (options.sparkline && timeColumn >= 0) {
|
if (options.sparkline && timeField) {
|
||||||
sparkline = getFlotPairs({
|
sparkline = getFlotPairs({
|
||||||
xField: series.fields[timeColumn],
|
xField: timeField,
|
||||||
yField: series.fields[i],
|
yField: series.fields[i],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { getFlotPairs } from './flotPairs';
|
import { getFlotPairs } from './flotPairs';
|
||||||
import { DataFrameHelper } from '@grafana/data';
|
import { MutableDataFrame } from '@grafana/data';
|
||||||
|
|
||||||
describe('getFlotPairs', () => {
|
describe('getFlotPairs', () => {
|
||||||
const series = new DataFrameHelper({
|
const series = new MutableDataFrame({
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'a', values: [1, 2, 3] },
|
{ name: 'a', values: [1, 2, 3] },
|
||||||
{ name: 'b', values: [100, 200, 300] },
|
{ name: 'b', values: [100, 200, 300] },
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ import {
|
|||||||
LogsMetaItem,
|
LogsMetaItem,
|
||||||
LogsMetaKind,
|
LogsMetaKind,
|
||||||
LogsDedupStrategy,
|
LogsDedupStrategy,
|
||||||
DataFrameHelper,
|
|
||||||
GraphSeriesXY,
|
GraphSeriesXY,
|
||||||
LoadingState,
|
LoadingState,
|
||||||
dateTime,
|
dateTime,
|
||||||
toUtc,
|
toUtc,
|
||||||
NullValueMode,
|
NullValueMode,
|
||||||
toDataFrame,
|
toDataFrame,
|
||||||
|
FieldCache,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { getThemeColor } from 'app/core/utils/colors';
|
import { getThemeColor } from 'app/core/utils/colors';
|
||||||
import { hasAnsiCodes } from 'app/core/utils/text';
|
import { hasAnsiCodes } from 'app/core/utils/text';
|
||||||
@@ -238,22 +238,22 @@ export function logSeriesToLogsModel(logSeries: DataFrame[]): LogsModel {
|
|||||||
|
|
||||||
for (let i = 0; i < logSeries.length; i++) {
|
for (let i = 0; i < logSeries.length; i++) {
|
||||||
const series = logSeries[i];
|
const series = logSeries[i];
|
||||||
const data = new DataFrameHelper(series);
|
const fieldCache = new FieldCache(series);
|
||||||
const uniqueLabels = findUniqueLabels(series.labels, commonLabels);
|
const uniqueLabels = findUniqueLabels(series.labels, commonLabels);
|
||||||
if (Object.keys(uniqueLabels).length > 0) {
|
if (Object.keys(uniqueLabels).length > 0) {
|
||||||
hasUniqueLabels = true;
|
hasUniqueLabels = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeFieldIndex = data.getFirstFieldOfType(FieldType.time);
|
const timeFieldIndex = fieldCache.getFirstFieldOfType(FieldType.time);
|
||||||
const stringField = data.getFirstFieldOfType(FieldType.string);
|
const stringField = fieldCache.getFirstFieldOfType(FieldType.string);
|
||||||
const logLevelField = data.getFieldByName('level');
|
const logLevelField = fieldCache.getFieldByName('level');
|
||||||
|
|
||||||
let seriesLogLevel: LogLevel | undefined = undefined;
|
let seriesLogLevel: LogLevel | undefined = undefined;
|
||||||
if (series.labels && Object.keys(series.labels).indexOf('level') !== -1) {
|
if (series.labels && Object.keys(series.labels).indexOf('level') !== -1) {
|
||||||
seriesLogLevel = getLogLevelFromKey(series.labels['level']);
|
seriesLogLevel = getLogLevelFromKey(series.labels['level']);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let j = 0; j < data.length; j++) {
|
for (let j = 0; j < series.length; j++) {
|
||||||
const ts = timeFieldIndex.values.get(j);
|
const ts = timeFieldIndex.values.get(j);
|
||||||
const time = dateTime(ts);
|
const time = dateTime(ts);
|
||||||
const timeEpochMs = time.valueOf();
|
const timeEpochMs = time.valueOf();
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
LogsMetaKind,
|
LogsMetaKind,
|
||||||
LogsDedupStrategy,
|
LogsDedupStrategy,
|
||||||
LogLevel,
|
LogLevel,
|
||||||
DataFrameHelper,
|
MutableDataFrame,
|
||||||
toDataFrame,
|
toDataFrame,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { dedupLogRows, dataFrameToLogsModel } from '../logs_model';
|
import { dedupLogRows, dataFrameToLogsModel } from '../logs_model';
|
||||||
@@ -168,7 +168,7 @@ describe('dataFrameToLogsModel', () => {
|
|||||||
|
|
||||||
it('given series without a time field should return empty logs model', () => {
|
it('given series without a time field should return empty logs model', () => {
|
||||||
const series: DataFrame[] = [
|
const series: DataFrame[] = [
|
||||||
new DataFrameHelper({
|
new MutableDataFrame({
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
name: 'message',
|
name: 'message',
|
||||||
@@ -183,7 +183,7 @@ describe('dataFrameToLogsModel', () => {
|
|||||||
|
|
||||||
it('given series without a string field should return empty logs model', () => {
|
it('given series without a string field should return empty logs model', () => {
|
||||||
const series: DataFrame[] = [
|
const series: DataFrame[] = [
|
||||||
new DataFrameHelper({
|
new MutableDataFrame({
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
name: 'time',
|
name: 'time',
|
||||||
@@ -198,7 +198,7 @@ describe('dataFrameToLogsModel', () => {
|
|||||||
|
|
||||||
it('given one series should return expected logs model', () => {
|
it('given one series should return expected logs model', () => {
|
||||||
const series: DataFrame[] = [
|
const series: DataFrame[] = [
|
||||||
new DataFrameHelper({
|
new MutableDataFrame({
|
||||||
labels: {
|
labels: {
|
||||||
filename: '/var/log/grafana/grafana.log',
|
filename: '/var/log/grafana/grafana.log',
|
||||||
job: 'grafana',
|
job: 'grafana',
|
||||||
@@ -259,7 +259,7 @@ describe('dataFrameToLogsModel', () => {
|
|||||||
|
|
||||||
it('given one series without labels should return expected logs model', () => {
|
it('given one series without labels should return expected logs model', () => {
|
||||||
const series: DataFrame[] = [
|
const series: DataFrame[] = [
|
||||||
new DataFrameHelper({
|
new MutableDataFrame({
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
name: 'time',
|
name: 'time',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { PanelQueryRunner, QueryRunnerOptions } from './PanelQueryRunner';
|
import { PanelQueryRunner, QueryRunnerOptions } from './PanelQueryRunner';
|
||||||
import { PanelData, DataQueryRequest, DataStreamObserver, DataStreamState, ScopedVars } from '@grafana/ui';
|
import { PanelData, DataQueryRequest, DataStreamObserver, DataStreamState, ScopedVars } from '@grafana/ui';
|
||||||
|
|
||||||
import { LoadingState, DataFrameHelper } from '@grafana/data';
|
import { LoadingState, MutableDataFrame } from '@grafana/data';
|
||||||
import { dateTime } from '@grafana/data';
|
import { dateTime } from '@grafana/data';
|
||||||
import { SHARED_DASHBODARD_QUERY } from 'app/plugins/datasource/dashboard/SharedQueryRunner';
|
import { SHARED_DASHBODARD_QUERY } from 'app/plugins/datasource/dashboard/SharedQueryRunner';
|
||||||
import { DashboardQuery } from 'app/plugins/datasource/dashboard/types';
|
import { DashboardQuery } from 'app/plugins/datasource/dashboard/types';
|
||||||
@@ -200,7 +200,7 @@ describe('PanelQueryRunner', () => {
|
|||||||
state: LoadingState.Streaming,
|
state: LoadingState.Streaming,
|
||||||
key: 'test-stream-1',
|
key: 'test-stream-1',
|
||||||
data: [
|
data: [
|
||||||
new DataFrameHelper({
|
new MutableDataFrame({
|
||||||
fields: [],
|
fields: [],
|
||||||
name: 'I am a magic stream',
|
name: 'I am a magic stream',
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -163,6 +163,8 @@ describe('ResultProcessor', () => {
|
|||||||
const { resultProcessor } = testContext({ mode: ExploreMode.Logs, observerResponse: null });
|
const { resultProcessor } = testContext({ mode: ExploreMode.Logs, observerResponse: null });
|
||||||
const theResult = resultProcessor.getLogsResult();
|
const theResult = resultProcessor.getLogsResult();
|
||||||
|
|
||||||
|
console.log(JSON.stringify(theResult));
|
||||||
|
|
||||||
expect(theResult).toEqual({
|
expect(theResult).toEqual({
|
||||||
hasUniqueLabels: false,
|
hasUniqueLabels: false,
|
||||||
meta: [],
|
meta: [],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
|
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
|
||||||
import { FieldDisplay, ScopedVars, DataLinkBuiltInVars } from '@grafana/ui';
|
import { FieldDisplay, ScopedVars, DataLinkBuiltInVars } from '@grafana/ui';
|
||||||
import { LinkModelSupplier, DataFrameHelper, FieldType } from '@grafana/data';
|
import { LinkModelSupplier, getTimeField } from '@grafana/data';
|
||||||
import { getLinkSrv } from './link_srv';
|
import { getLinkSrv } from './link_srv';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,9 +28,9 @@ export const getFieldLinksSupplier = (value: FieldDisplay): LinkModelSupplier<Fi
|
|||||||
if (value.row) {
|
if (value.row) {
|
||||||
const row = value.view.get(value.row);
|
const row = value.view.get(value.row);
|
||||||
console.log('ROW:', row);
|
console.log('ROW:', row);
|
||||||
const dataFrame = new DataFrameHelper(value.view.dataFrame);
|
const dataFrame = value.view.dataFrame;
|
||||||
|
|
||||||
const timeField = dataFrame.getFirstFieldOfType(FieldType.time);
|
const { timeField } = getTimeField(dataFrame);
|
||||||
if (timeField) {
|
if (timeField) {
|
||||||
scopedVars[DataLinkBuiltInVars.valueTime] = {
|
scopedVars[DataLinkBuiltInVars.valueTime] = {
|
||||||
text: 'Value time',
|
text: 'Value time',
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import _ from 'lodash';
|
|||||||
import flatten from 'app/core/utils/flatten';
|
import flatten from 'app/core/utils/flatten';
|
||||||
import * as queryDef from './query_def';
|
import * as queryDef from './query_def';
|
||||||
import TableModel from 'app/core/table_model';
|
import TableModel from 'app/core/table_model';
|
||||||
import { DataFrame, toDataFrame, FieldType, DataFrameHelper } from '@grafana/data';
|
import { DataFrame, toDataFrame, FieldType, MutableDataFrame } from '@grafana/data';
|
||||||
import { DataQueryResponse } from '@grafana/ui';
|
import { DataQueryResponse } from '@grafana/ui';
|
||||||
import { ElasticsearchAggregation } from './types';
|
import { ElasticsearchAggregation } from './types';
|
||||||
|
|
||||||
@@ -464,7 +464,7 @@ export class ElasticResponse {
|
|||||||
|
|
||||||
if (docs.length > 0) {
|
if (docs.length > 0) {
|
||||||
propNames = propNames.sort();
|
propNames = propNames.sort();
|
||||||
const series = new DataFrameHelper({ fields: [] });
|
const series = new MutableDataFrame({ fields: [] });
|
||||||
|
|
||||||
series.addField({
|
series.addField({
|
||||||
name: this.targets[0].timeField,
|
name: this.targets[0].timeField,
|
||||||
@@ -513,7 +513,7 @@ export class ElasticResponse {
|
|||||||
|
|
||||||
// Add a row for each document
|
// Add a row for each document
|
||||||
for (const doc of docs) {
|
for (const doc of docs) {
|
||||||
series.appendRowFrom(doc);
|
series.add(doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
dataFrame.push(series);
|
dataFrame.push(series);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { DataFrameView, KeyValue, MutableDataFrame } from '@grafana/data';
|
||||||
import { ElasticResponse } from '../elastic_response';
|
import { ElasticResponse } from '../elastic_response';
|
||||||
import { DataFrameHelper, DataFrameView, KeyValue } from '@grafana/data';
|
|
||||||
|
|
||||||
describe('ElasticResponse', () => {
|
describe('ElasticResponse', () => {
|
||||||
let targets;
|
let targets;
|
||||||
@@ -859,7 +859,7 @@ describe('ElasticResponse', () => {
|
|||||||
|
|
||||||
it('should return histogram aggregation and documents', () => {
|
it('should return histogram aggregation and documents', () => {
|
||||||
expect(result.data.length).toBe(2);
|
expect(result.data.length).toBe(2);
|
||||||
const logResults = result.data[0] as DataFrameHelper;
|
const logResults = result.data[0] as MutableDataFrame;
|
||||||
const fields = logResults.fields.map(f => {
|
const fields = logResults.fields.map(f => {
|
||||||
return {
|
return {
|
||||||
name: f.name,
|
name: f.name,
|
||||||
@@ -874,16 +874,15 @@ describe('ElasticResponse', () => {
|
|||||||
let rows = new DataFrameView(logResults);
|
let rows = new DataFrameView(logResults);
|
||||||
for (let i = 0; i < rows.length; i++) {
|
for (let i = 0; i < rows.length; i++) {
|
||||||
const r = rows.get(i);
|
const r = rows.get(i);
|
||||||
const row = [r._id, r._type, r._index, r._source];
|
expect(r._id).toEqual(response.responses[0].hits.hits[i]._id);
|
||||||
expect(row).toContain(response.responses[0].hits.hits[i]._id);
|
expect(r._type).toEqual(response.responses[0].hits.hits[i]._type);
|
||||||
expect(row).toContain(response.responses[0].hits.hits[i]._type);
|
expect(r._index).toEqual(response.responses[0].hits.hits[i]._index);
|
||||||
expect(row).toContain(response.responses[0].hits.hits[i]._index);
|
expect(r._source).toEqual(response.responses[0].hits.hits[i]._source);
|
||||||
expect(row).toContain(JSON.stringify(response.responses[0].hits.hits[i]._source, undefined, 2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a map from the histogram results
|
// Make a map from the histogram results
|
||||||
const hist: KeyValue<number> = {};
|
const hist: KeyValue<number> = {};
|
||||||
const histogramResults = new DataFrameHelper(result.data[1]);
|
const histogramResults = new MutableDataFrame(result.data[1]);
|
||||||
rows = new DataFrameView(histogramResults);
|
rows = new DataFrameView(histogramResults);
|
||||||
for (let i = 0; i < rows.length; i++) {
|
for (let i = 0; i < rows.length; i++) {
|
||||||
const row = rows.get(i);
|
const row = rows.get(i);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import React, { PureComponent } from 'react';
|
|||||||
import { InputOptions } from './types';
|
import { InputOptions } from './types';
|
||||||
|
|
||||||
import { DataSourcePluginOptionsEditorProps, DataSourceSettings, TableInputCSV } from '@grafana/ui';
|
import { DataSourcePluginOptionsEditorProps, DataSourceSettings, TableInputCSV } from '@grafana/ui';
|
||||||
import { DataFrame, DataFrameHelper } from '@grafana/data';
|
import { DataFrame, MutableDataFrame } from '@grafana/data';
|
||||||
import { dataFrameToCSV } from './utils';
|
import { dataFrameToCSV } from './utils';
|
||||||
|
|
||||||
type InputSettings = DataSourceSettings<InputOptions>;
|
type InputSettings = DataSourceSettings<InputOptions>;
|
||||||
@@ -32,7 +32,7 @@ export class InputConfigEditor extends PureComponent<Props, State> {
|
|||||||
onSeriesParsed = (data: DataFrame[], text: string) => {
|
onSeriesParsed = (data: DataFrame[], text: string) => {
|
||||||
const { options, onOptionsChange } = this.props;
|
const { options, onOptionsChange } = this.props;
|
||||||
if (!data) {
|
if (!data) {
|
||||||
data = [new DataFrameHelper()];
|
data = [new MutableDataFrame()];
|
||||||
}
|
}
|
||||||
// data is a property on 'jsonData'
|
// data is a property on 'jsonData'
|
||||||
const jsonData = {
|
const jsonData = {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import InputDatasource, { describeDataFrame } from './InputDatasource';
|
import InputDatasource, { describeDataFrame } from './InputDatasource';
|
||||||
import { InputQuery, InputOptions } from './types';
|
import { InputQuery, InputOptions } from './types';
|
||||||
import { readCSV, DataFrame, DataFrameHelper } from '@grafana/data';
|
import { readCSV, DataFrame, MutableDataFrame } from '@grafana/data';
|
||||||
import { DataSourceInstanceSettings, PluginMeta } from '@grafana/ui';
|
import { DataSourceInstanceSettings, PluginMeta } from '@grafana/ui';
|
||||||
import { getQueryOptions } from 'test/helpers/getQueryOptions';
|
import { getQueryOptions } from 'test/helpers/getQueryOptions';
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ describe('InputDatasource', () => {
|
|||||||
expect(describeDataFrame(null)).toEqual('');
|
expect(describeDataFrame(null)).toEqual('');
|
||||||
expect(
|
expect(
|
||||||
describeDataFrame([
|
describeDataFrame([
|
||||||
new DataFrameHelper({
|
new MutableDataFrame({
|
||||||
name: 'x',
|
name: 'x',
|
||||||
fields: [{ name: 'a' }],
|
fields: [{ name: 'a' }],
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { InputDatasource, describeDataFrame } from './InputDatasource';
|
|||||||
import { InputQuery, InputOptions } from './types';
|
import { InputQuery, InputOptions } from './types';
|
||||||
|
|
||||||
import { FormLabel, Select, QueryEditorProps, TableInputCSV } from '@grafana/ui';
|
import { FormLabel, Select, QueryEditorProps, TableInputCSV } from '@grafana/ui';
|
||||||
import { DataFrame, toCSV, SelectableValue, DataFrameHelper } from '@grafana/data';
|
import { DataFrame, toCSV, SelectableValue, MutableDataFrame } from '@grafana/data';
|
||||||
|
|
||||||
import { dataFrameToCSV } from './utils';
|
import { dataFrameToCSV } from './utils';
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ export class InputQueryEditor extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
data = [...datasource.data];
|
data = [...datasource.data];
|
||||||
if (!data) {
|
if (!data) {
|
||||||
data = [new DataFrameHelper()];
|
data = [new MutableDataFrame()];
|
||||||
}
|
}
|
||||||
this.setState({ text: toCSV(data) });
|
this.setState({ text: toCSV(data) });
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,7 @@ export class InputQueryEditor extends PureComponent<Props, State> {
|
|||||||
const { query, onChange, onRunQuery } = this.props;
|
const { query, onChange, onRunQuery } = this.props;
|
||||||
this.setState({ text });
|
this.setState({ text });
|
||||||
if (!data) {
|
if (!data) {
|
||||||
data = [new DataFrameHelper()];
|
data = [new MutableDataFrame()];
|
||||||
}
|
}
|
||||||
onChange({ ...query, data });
|
onChange({ ...query, data });
|
||||||
onRunQuery();
|
onRunQuery();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { LokiLogsStream } from './types';
|
import { LokiLogsStream } from './types';
|
||||||
import { parseLabels, FieldType, Labels, DataFrameHelper } from '@grafana/data';
|
import { parseLabels, FieldType, Labels, MutableDataFrame } from '@grafana/data';
|
||||||
|
|
||||||
export function logStreamToDataFrame(stream: LokiLogsStream, refId?: string): DataFrameHelper {
|
export function logStreamToDataFrame(stream: LokiLogsStream, refId?: string): MutableDataFrame {
|
||||||
let labels: Labels = stream.parsedLabels;
|
let labels: Labels = stream.parsedLabels;
|
||||||
if (!labels && stream.labels) {
|
if (!labels && stream.labels) {
|
||||||
labels = parseLabels(stream.labels);
|
labels = parseLabels(stream.labels);
|
||||||
@@ -14,7 +14,7 @@ export function logStreamToDataFrame(stream: LokiLogsStream, refId?: string): Da
|
|||||||
lines.push(entry.line);
|
lines.push(entry.line);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new DataFrameHelper({
|
return new MutableDataFrame({
|
||||||
refId,
|
refId,
|
||||||
labels,
|
labels,
|
||||||
fields: [
|
fields: [
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
LoadingState,
|
LoadingState,
|
||||||
LogLevel,
|
LogLevel,
|
||||||
CSVReader,
|
CSVReader,
|
||||||
DataFrameHelper,
|
MutableDataFrame,
|
||||||
CircularVector,
|
CircularVector,
|
||||||
DataFrame,
|
DataFrame,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
@@ -182,7 +182,7 @@ export class SignalWorker extends StreamWorker {
|
|||||||
const vals = new CircularVector({ capacity: maxRows });
|
const vals = new CircularVector({ capacity: maxRows });
|
||||||
this.values = [times, vals];
|
this.values = [times, vals];
|
||||||
|
|
||||||
const data = new DataFrameHelper({
|
const data = new MutableDataFrame({
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'Time', type: FieldType.time, values: times }, // The time field
|
{ name: 'Time', type: FieldType.time, values: times }, // The time field
|
||||||
{ name: 'Value', type: FieldType.number, values: vals },
|
{ name: 'Value', type: FieldType.number, values: vals },
|
||||||
@@ -351,7 +351,7 @@ export class LogsWorker extends StreamWorker {
|
|||||||
const lines = new CircularVector({ capacity: maxRows });
|
const lines = new CircularVector({ capacity: maxRows });
|
||||||
|
|
||||||
this.values = [times, lines];
|
this.values = [times, lines];
|
||||||
this.data = new DataFrameHelper({
|
this.data = new MutableDataFrame({
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'Time', type: FieldType.time, values: times },
|
{ name: 'Time', type: FieldType.time, values: times },
|
||||||
{ name: 'Line', type: FieldType.string, values: lines },
|
{ name: 'Line', type: FieldType.string, values: lines },
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { colors, getColorFromHexRgbOrName } from '@grafana/ui';
|
import { colors, getColorFromHexRgbOrName } from '@grafana/ui';
|
||||||
import { TimeRange, FieldType, Field, DataFrame, DataFrameHelper } from '@grafana/data';
|
import { TimeRange, FieldType, Field, DataFrame, getTimeField } from '@grafana/data';
|
||||||
import TimeSeries from 'app/core/time_series2';
|
import TimeSeries from 'app/core/time_series2';
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
|
|
||||||
@@ -21,15 +21,17 @@ export class DataProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const series of dataList) {
|
for (const series of dataList) {
|
||||||
const data = new DataFrameHelper(series);
|
const { timeField } = getTimeField(series);
|
||||||
const time = data.getFirstFieldOfType(FieldType.time);
|
if (!timeField) {
|
||||||
|
|
||||||
if (!time) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const seriesName = series.name ? series.name : series.refId;
|
const seriesName = series.name ? series.name : series.refId;
|
||||||
for (const field of data.getFields(FieldType.number)) {
|
for (const field of series.fields) {
|
||||||
|
if (field.type !== FieldType.number) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let name = field.config && field.config.title ? field.config.title : field.name;
|
let name = field.config && field.config.title ? field.config.title : field.name;
|
||||||
|
|
||||||
if (seriesName && dataList.length > 0 && name !== seriesName) {
|
if (seriesName && dataList.length > 0 && name !== seriesName) {
|
||||||
@@ -37,8 +39,8 @@ export class DataProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const datapoints = [];
|
const datapoints = [];
|
||||||
for (let r = 0; r < data.length; r++) {
|
for (let r = 0; r < series.length; r++) {
|
||||||
datapoints.push([field.values.get(r), time.values.get(r)]);
|
datapoints.push([field.values.get(r), timeField.values.get(r)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
list.push(this.toTimeSeries(field, name, datapoints, list.length, range));
|
list.push(this.toTimeSeries(field, name, datapoints, list.length, range));
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { colors, getFlotPairs, getColorFromHexRgbOrName, getDisplayProcessor, PanelData } from '@grafana/ui';
|
import { colors, getFlotPairs, getColorFromHexRgbOrName, getDisplayProcessor, PanelData } from '@grafana/ui';
|
||||||
import { NullValueMode, reduceField, DataFrameHelper, FieldType, DisplayValue, GraphSeriesXY } from '@grafana/data';
|
import { NullValueMode, reduceField, FieldType, DisplayValue, GraphSeriesXY, getTimeField } from '@grafana/data';
|
||||||
|
|
||||||
import { SeriesOptions, GraphOptions } from './types';
|
import { SeriesOptions, GraphOptions } from './types';
|
||||||
import { GraphLegendEditorLegendOptions } from './GraphLegendEditor';
|
import { GraphLegendEditorLegendOptions } from './GraphLegendEditor';
|
||||||
@@ -19,16 +19,19 @@ export const getGraphSeriesModel = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (const series of data.series) {
|
for (const series of data.series) {
|
||||||
const data = new DataFrameHelper(series);
|
const { timeField } = getTimeField(series);
|
||||||
const timeColumn = data.getFirstFieldOfType(FieldType.time);
|
if (!timeField) {
|
||||||
if (!timeColumn) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const field of data.getFields(FieldType.number)) {
|
for (const field of series.fields) {
|
||||||
|
if (field.type !== FieldType.number) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Use external calculator just to make sure it works :)
|
// Use external calculator just to make sure it works :)
|
||||||
const points = getFlotPairs({
|
const points = getFlotPairs({
|
||||||
xField: timeColumn,
|
xField: timeField,
|
||||||
yField: field,
|
yField: field,
|
||||||
nullValueMode: NullValueMode.Null,
|
nullValueMode: NullValueMode.Null,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user