mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
grafana/data: Reorganise code (#19136)
* Organise data frame and vectors code
* Organise transformations
* Move dataframe utils to dataframe dir
* Organise datetime utils
* Organise text utils
* Organise logs utils
* Revert "Organise logs utils"
This reverts commit c24115c755.
* registry -> Registry
* Transformations reorg
This commit is contained in:
22
packages/grafana-data/src/dataframe/CircularDataFrame.ts
Normal file
22
packages/grafana-data/src/dataframe/CircularDataFrame.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { MutableDataFrame } from './MutableDataFrame';
|
||||||
|
import { CircularVector } from '../vector/CircularVector';
|
||||||
|
|
||||||
|
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({
|
||||||
|
...options,
|
||||||
|
buffer,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { FieldType, DataFrameDTO } from '../types/index';
|
import { FieldType, DataFrameDTO } from '../types/dataFrame';
|
||||||
import { MutableDataFrame } from './dataFrameHelper';
|
import { DateTime } from '../datetime/moment_wrapper';
|
||||||
import { DataFrameView } from './dataFrameView';
|
import { MutableDataFrame } from './MutableDataFrame';
|
||||||
import { DateTime } from './moment_wrapper';
|
import { DataFrameView } from './DataFrameView';
|
||||||
|
|
||||||
interface MySpecialObject {
|
interface MySpecialObject {
|
||||||
time: DateTime;
|
time: DateTime;
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { DataFrame, Vector } from '../types/index';
|
import { Vector } from '../types/vector';
|
||||||
|
import { DataFrame } from '../types/dataFrame';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This abstraction will present the contents of a DataFrame as if
|
* This abstraction will present the contents of a DataFrame as if
|
||||||
@@ -1,30 +1,7 @@
|
|||||||
import { DataFrameDTO, FieldType } from '../types';
|
import { FieldCache } from './FieldCache';
|
||||||
import { FieldCache, MutableDataFrame } from './dataFrameHelper';
|
import { FieldType } from '../types/dataFrame';
|
||||||
import { toDataFrame } from './processDataFrame';
|
import { toDataFrame } from './processDataFrame';
|
||||||
|
|
||||||
describe('dataFrameHelper', () => {
|
|
||||||
const frame = toDataFrame({
|
|
||||||
fields: [
|
|
||||||
{ name: 'time', type: FieldType.time, values: [100, 200, 300] },
|
|
||||||
{ name: 'name', type: FieldType.string, values: ['a', 'b', 'c'] },
|
|
||||||
{ name: 'value', type: FieldType.number, values: [1, 2, 3] },
|
|
||||||
{ name: 'value', type: FieldType.number, values: [4, 5, 6] },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
const ext = new FieldCache(frame);
|
|
||||||
|
|
||||||
it('should get the first field with a duplicate name', () => {
|
|
||||||
const field = ext.getFieldByName('value');
|
|
||||||
expect(field!.name).toEqual('value');
|
|
||||||
expect(field!.values.toJSON()).toEqual([1, 2, 3]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return index of the field', () => {
|
|
||||||
const field = ext.getFirstFieldOfType(FieldType.number);
|
|
||||||
expect(field!.index).toEqual(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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 frame = toDataFrame({
|
const frame = toDataFrame({
|
||||||
@@ -90,68 +67,27 @@ describe('FieldCache', () => {
|
|||||||
expect(fieldCache.getFieldByName('undefined')!.name).toEqual(expectedFieldNames[5]);
|
expect(fieldCache.getFieldByName('undefined')!.name).toEqual(expectedFieldNames[5]);
|
||||||
expect(fieldCache.getFieldByName('null')).toBeUndefined();
|
expect(fieldCache.getFieldByName('null')).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('reverse', () => {
|
describe('field retrieval', () => {
|
||||||
describe('when called with a DataFrame', () => {
|
const frame = toDataFrame({
|
||||||
it('then it should reverse the order of values in all fields', () => {
|
fields: [
|
||||||
const frame: DataFrameDTO = {
|
{ name: 'time', type: FieldType.time, values: [100, 200, 300] },
|
||||||
fields: [
|
{ name: 'name', type: FieldType.string, values: ['a', 'b', 'c'] },
|
||||||
{ name: 'time', type: FieldType.time, values: [100, 200, 300] },
|
{ name: 'value', type: FieldType.number, values: [1, 2, 3] },
|
||||||
{ name: 'name', type: FieldType.string, values: ['a', 'b', 'c'] },
|
{ name: 'value', type: FieldType.number, values: [4, 5, 6] },
|
||||||
{ name: 'value', type: FieldType.number, values: [1, 2, 3] },
|
],
|
||||||
],
|
});
|
||||||
};
|
const ext = new FieldCache(frame);
|
||||||
|
|
||||||
const helper = new MutableDataFrame(frame);
|
it('should get the first field with a duplicate name', () => {
|
||||||
|
const field = ext.getFieldByName('value');
|
||||||
|
expect(field!.name).toEqual('value');
|
||||||
|
expect(field!.values.toJSON()).toEqual([1, 2, 3]);
|
||||||
|
});
|
||||||
|
|
||||||
expect(helper.values.time.toArray()).toEqual([100, 200, 300]);
|
it('should return index of the field', () => {
|
||||||
expect(helper.values.name.toArray()).toEqual(['a', 'b', 'c']);
|
const field = ext.getFirstFieldOfType(FieldType.number);
|
||||||
expect(helper.values.value.toArray()).toEqual([1, 2, 3]);
|
expect(field!.index).toEqual(2);
|
||||||
|
|
||||||
helper.reverse();
|
|
||||||
|
|
||||||
expect(helper.values.time.toArray()).toEqual([300, 200, 100]);
|
|
||||||
expect(helper.values.name.toArray()).toEqual(['c', 'b', 'a']);
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
78
packages/grafana-data/src/dataframe/FieldCache.ts
Normal file
78
packages/grafana-data/src/dataframe/FieldCache.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { Field, DataFrame, FieldType, guessFieldTypeForField } from '../index';
|
||||||
|
|
||||||
|
interface FieldWithIndex extends Field {
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FieldCache {
|
||||||
|
fields: FieldWithIndex[] = [];
|
||||||
|
|
||||||
|
private fieldByName: { [key: string]: FieldWithIndex } = {};
|
||||||
|
private fieldByType: { [key: string]: FieldWithIndex[] } = {};
|
||||||
|
|
||||||
|
constructor(data: DataFrame) {
|
||||||
|
this.fields = data.fields.map((field, idx) => ({
|
||||||
|
...field,
|
||||||
|
index: idx,
|
||||||
|
}));
|
||||||
|
|
||||||
|
for (let i = 0; i < data.fields.length; i++) {
|
||||||
|
const field = data.fields[i];
|
||||||
|
// 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]) {
|
||||||
|
this.fieldByType[field.type] = [];
|
||||||
|
}
|
||||||
|
this.fieldByType[field.type].push({
|
||||||
|
...field,
|
||||||
|
index: i,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.fieldByName[field.name]) {
|
||||||
|
console.warn('Duplicate field names in DataFrame: ', field.name);
|
||||||
|
} else {
|
||||||
|
this.fieldByName[field.name] = { ...field, index: i };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getFields(type?: FieldType): FieldWithIndex[] {
|
||||||
|
if (!type) {
|
||||||
|
return [...this.fields]; // All fields
|
||||||
|
}
|
||||||
|
const fields = this.fieldByType[type];
|
||||||
|
if (fields) {
|
||||||
|
return [...fields];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
hasFieldOfType(type: FieldType): boolean {
|
||||||
|
const types = this.fieldByType[type];
|
||||||
|
return types && types.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFirstFieldOfType(type: FieldType): FieldWithIndex | undefined {
|
||||||
|
const arr = this.fieldByType[type];
|
||||||
|
if (arr && arr.length > 0) {
|
||||||
|
return arr[0];
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasFieldNamed(name: string): boolean {
|
||||||
|
return !!this.fieldByName[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first field with the given name.
|
||||||
|
*/
|
||||||
|
getFieldByName(name: string): FieldWithIndex | undefined {
|
||||||
|
return this.fieldByName[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
66
packages/grafana-data/src/dataframe/MutableDataFrame.test.ts
Normal file
66
packages/grafana-data/src/dataframe/MutableDataFrame.test.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { DataFrameDTO, FieldType } from '../types/dataFrame';
|
||||||
|
import { MutableDataFrame } from './MutableDataFrame';
|
||||||
|
|
||||||
|
describe('Reversing DataFrame', () => {
|
||||||
|
describe('when called with a DataFrame', () => {
|
||||||
|
it('then it should reverse the order of values in all fields', () => {
|
||||||
|
const frame: DataFrameDTO = {
|
||||||
|
fields: [
|
||||||
|
{ name: 'time', type: FieldType.time, values: [100, 200, 300] },
|
||||||
|
{ name: 'name', type: FieldType.string, values: ['a', 'b', 'c'] },
|
||||||
|
{ name: 'value', type: FieldType.number, values: [1, 2, 3] },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const helper = new MutableDataFrame(frame);
|
||||||
|
|
||||||
|
expect(helper.values.time.toArray()).toEqual([100, 200, 300]);
|
||||||
|
expect(helper.values.name.toArray()).toEqual(['a', 'b', 'c']);
|
||||||
|
expect(helper.values.value.toArray()).toEqual([1, 2, 3]);
|
||||||
|
|
||||||
|
helper.reverse();
|
||||||
|
|
||||||
|
expect(helper.values.time.toArray()).toEqual([300, 200, 100]);
|
||||||
|
expect(helper.values.name.toArray()).toEqual(['c', 'b', 'a']);
|
||||||
|
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,111 +1,12 @@
|
|||||||
import { Field, FieldType, DataFrame, Vector, FieldDTO, DataFrameDTO } from '../types/dataFrame';
|
import { Field, DataFrame, DataFrameDTO, FieldDTO, FieldType } from '../types/dataFrame';
|
||||||
import { Labels, QueryResultMeta, KeyValue } from '../types/data';
|
import { KeyValue, QueryResultMeta, Labels } from '../types/data';
|
||||||
import { guessFieldTypeForField, guessFieldTypeFromValue, toDataFrameDTO } from './processDataFrame';
|
import { guessFieldTypeFromValue, guessFieldTypeForField, toDataFrameDTO } from './processDataFrame';
|
||||||
import { ArrayVector, MutableVector, vectorToArray, CircularVector } from './vector';
|
|
||||||
import isArray from 'lodash/isArray';
|
import isArray from 'lodash/isArray';
|
||||||
import isString from 'lodash/isString';
|
import isString from 'lodash/isString';
|
||||||
|
import { makeFieldParser } from '../utils/fieldParser';
|
||||||
interface FieldWithIndex extends Field {
|
import { MutableVector, Vector } from '../types/vector';
|
||||||
index: number;
|
import { ArrayVector } from '../vector/ArrayVector';
|
||||||
}
|
import { vectorToArray } from '../vector/vectorToArray';
|
||||||
export class FieldCache {
|
|
||||||
fields: FieldWithIndex[] = [];
|
|
||||||
|
|
||||||
private fieldByName: { [key: string]: FieldWithIndex } = {};
|
|
||||||
private fieldByType: { [key: string]: FieldWithIndex[] } = {};
|
|
||||||
|
|
||||||
constructor(data: DataFrame) {
|
|
||||||
this.fields = data.fields.map((field, idx) => ({
|
|
||||||
...field,
|
|
||||||
index: idx,
|
|
||||||
}));
|
|
||||||
|
|
||||||
for (let i = 0; i < data.fields.length; i++) {
|
|
||||||
const field = data.fields[i];
|
|
||||||
// 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]) {
|
|
||||||
this.fieldByType[field.type] = [];
|
|
||||||
}
|
|
||||||
this.fieldByType[field.type].push({
|
|
||||||
...field,
|
|
||||||
index: i,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.fieldByName[field.name]) {
|
|
||||||
console.warn('Duplicate field names in DataFrame: ', field.name);
|
|
||||||
} else {
|
|
||||||
this.fieldByName[field.name] = { ...field, index: i };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getFields(type?: FieldType): FieldWithIndex[] {
|
|
||||||
if (!type) {
|
|
||||||
return [...this.fields]; // All fields
|
|
||||||
}
|
|
||||||
const fields = this.fieldByType[type];
|
|
||||||
if (fields) {
|
|
||||||
return [...fields];
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
hasFieldOfType(type: FieldType): boolean {
|
|
||||||
const types = this.fieldByType[type];
|
|
||||||
return types && types.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
getFirstFieldOfType(type: FieldType): FieldWithIndex | undefined {
|
|
||||||
const arr = this.fieldByType[type];
|
|
||||||
if (arr && arr.length > 0) {
|
|
||||||
return arr[0];
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasFieldNamed(name: string): boolean {
|
|
||||||
return !!this.fieldByName[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the first field with the given name.
|
|
||||||
*/
|
|
||||||
getFieldByName(name: string): FieldWithIndex | undefined {
|
|
||||||
return this.fieldByName[name];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeFieldParser(value: any, field: Field): (value: string) => any {
|
|
||||||
if (!field.type) {
|
|
||||||
if (field.name === 'time' || field.name === 'Time') {
|
|
||||||
field.type = FieldType.time;
|
|
||||||
} else {
|
|
||||||
field.type = guessFieldTypeFromValue(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (field.type === FieldType.number) {
|
|
||||||
return (value: string) => {
|
|
||||||
return parseFloat(value);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Will convert anything that starts with "T" to true
|
|
||||||
if (field.type === FieldType.boolean) {
|
|
||||||
return (value: string) => {
|
|
||||||
return !(value[0] === 'F' || value[0] === 'f' || value[0] === '0');
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Just pass the string back
|
|
||||||
return (value: string) => value;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MutableField<T = any> = Field<T, MutableVector<T>>;
|
export type MutableField<T = any> = Field<T, MutableVector<T>>;
|
||||||
|
|
||||||
@@ -380,23 +281,3 @@ export class MutableDataFrame<T = any> implements DataFrame, MutableVector<T> {
|
|||||||
return toDataFrameDTO(this);
|
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({
|
|
||||||
...options,
|
|
||||||
buffer,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
5
packages/grafana-data/src/dataframe/index.ts
Normal file
5
packages/grafana-data/src/dataframe/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export * from './DataFrameView';
|
||||||
|
export * from './FieldCache';
|
||||||
|
export * from './CircularDataFrame';
|
||||||
|
export * from './MutableDataFrame';
|
||||||
|
export * from './processDataFrame';
|
||||||
@@ -8,8 +8,8 @@ import {
|
|||||||
sortDataFrame,
|
sortDataFrame,
|
||||||
} 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 '../datetime/moment_wrapper';
|
||||||
import { MutableDataFrame } from './dataFrameHelper';
|
import { MutableDataFrame } from './MutableDataFrame';
|
||||||
|
|
||||||
describe('toDataFrame', () => {
|
describe('toDataFrame', () => {
|
||||||
it('converts timeseries to series', () => {
|
it('converts timeseries to series', () => {
|
||||||
@@ -17,10 +17,11 @@ import {
|
|||||||
FieldDTO,
|
FieldDTO,
|
||||||
DataFrameDTO,
|
DataFrameDTO,
|
||||||
} from '../types/index';
|
} from '../types/index';
|
||||||
import { isDateTime } from './moment_wrapper';
|
import { isDateTime } from '../datetime/moment_wrapper';
|
||||||
import { ArrayVector, SortedVector } from './vector';
|
import { deprecationWarning } from '../utils/deprecationWarning';
|
||||||
import { MutableDataFrame } from './dataFrameHelper';
|
import { ArrayVector } from '../vector/ArrayVector';
|
||||||
import { deprecationWarning } from './deprecationWarning';
|
import { MutableDataFrame } from './MutableDataFrame';
|
||||||
|
import { SortedVector } from '../vector/SortedVector';
|
||||||
|
|
||||||
function convertTableToDataFrame(table: TableData): DataFrame {
|
function convertTableToDataFrame(table: TableData): DataFrame {
|
||||||
const fields = table.columns.map(c => {
|
const fields = table.columns.map(c => {
|
||||||
@@ -2,7 +2,7 @@ import sinon, { SinonFakeTimers } from 'sinon';
|
|||||||
import each from 'lodash/each';
|
import each from 'lodash/each';
|
||||||
|
|
||||||
import * as dateMath from './datemath';
|
import * as dateMath from './datemath';
|
||||||
import { dateTime, DurationUnit, DateTime } from '../utils/moment_wrapper';
|
import { dateTime, DurationUnit, DateTime } from './moment_wrapper';
|
||||||
|
|
||||||
describe('DateMath', () => {
|
describe('DateMath', () => {
|
||||||
const spans: DurationUnit[] = ['s', 'm', 'h', 'd', 'w', 'M', 'y'];
|
const spans: DurationUnit[] = ['s', 'm', 'h', 'd', 'w', 'M', 'y'];
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import includes from 'lodash/includes';
|
import includes from 'lodash/includes';
|
||||||
import isDate from 'lodash/isDate';
|
import isDate from 'lodash/isDate';
|
||||||
import { DateTime, dateTime, dateTimeForTimeZone, ISO_8601, isDateTime, DurationUnit } from './moment_wrapper';
|
import { DateTime, dateTime, dateTimeForTimeZone, ISO_8601, isDateTime, DurationUnit } from './moment_wrapper';
|
||||||
import { TimeZone } from '../types';
|
import { TimeZone } from '../types/index';
|
||||||
|
|
||||||
const units: DurationUnit[] = ['y', 'M', 'w', 'd', 'h', 'm', 's'];
|
const units: DurationUnit[] = ['y', 'M', 'w', 'd', 'h', 'm', 's'];
|
||||||
|
|
||||||
5
packages/grafana-data/src/datetime/index.ts
Normal file
5
packages/grafana-data/src/datetime/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
// Names are too general to export globally
|
||||||
|
import * as dateMath from './datemath';
|
||||||
|
import * as rangeUtil from './rangeutil';
|
||||||
|
export * from './moment_wrapper';
|
||||||
|
export { dateMath, rangeUtil };
|
||||||
@@ -4,7 +4,7 @@ import groupBy from 'lodash/groupBy';
|
|||||||
import { RawTimeRange } from '../types/time';
|
import { RawTimeRange } from '../types/time';
|
||||||
|
|
||||||
import * as dateMath from './datemath';
|
import * as dateMath from './datemath';
|
||||||
import { isDateTime, DateTime } from '../utils/moment_wrapper';
|
import { isDateTime, DateTime } from './moment_wrapper';
|
||||||
|
|
||||||
const spans: { [key: string]: { display: string; section?: number } } = {
|
const spans: { [key: string]: { display: string; section?: number } } = {
|
||||||
s: { display: 'second' },
|
s: { display: 'second' },
|
||||||
@@ -1,2 +1,7 @@
|
|||||||
export * from './utils/index';
|
export * from './utils';
|
||||||
export * from './types/index';
|
export * from './types';
|
||||||
|
export * from './vector';
|
||||||
|
export * from './dataframe';
|
||||||
|
export * from './transformations';
|
||||||
|
export * from './datetime';
|
||||||
|
export * from './text';
|
||||||
|
|||||||
3
packages/grafana-data/src/text/index.ts
Normal file
3
packages/grafana-data/src/text/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './string';
|
||||||
|
export * from './markdown';
|
||||||
|
export * from './text';
|
||||||
@@ -3,9 +3,9 @@ import difference from 'lodash/difference';
|
|||||||
import { fieldReducers, ReducerID, reduceField } from './fieldReducer';
|
import { fieldReducers, ReducerID, reduceField } from './fieldReducer';
|
||||||
|
|
||||||
import { Field, FieldType } from '../types/index';
|
import { Field, FieldType } from '../types/index';
|
||||||
import { MutableDataFrame } from './dataFrameHelper';
|
import { guessFieldTypeFromValue } from '../dataframe/processDataFrame';
|
||||||
import { ArrayVector } from './vector';
|
import { MutableDataFrame } from '../dataframe/MutableDataFrame';
|
||||||
import { guessFieldTypeFromValue } from './processDataFrame';
|
import { ArrayVector } from '../vector/ArrayVector';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run a reducer and get back the value
|
* Run a reducer and get back the value
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
// Libraries
|
// Libraries
|
||||||
import isNumber from 'lodash/isNumber';
|
import isNumber from 'lodash/isNumber';
|
||||||
|
|
||||||
import { NullValueMode, Field } from '../types';
|
import { NullValueMode, Field } from '../types/index';
|
||||||
import { Registry, RegistryItem } from './registry';
|
import { Registry, RegistryItem } from '../utils/Registry';
|
||||||
|
|
||||||
export enum ReducerID {
|
export enum ReducerID {
|
||||||
sum = 'sum',
|
sum = 'sum',
|
||||||
7
packages/grafana-data/src/transformations/index.ts
Normal file
7
packages/grafana-data/src/transformations/index.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export * from './matchers/ids';
|
||||||
|
export * from './transformers/ids';
|
||||||
|
export * from './matchers';
|
||||||
|
export * from './transformers';
|
||||||
|
export * from './fieldReducer';
|
||||||
|
export { FilterFieldsByNameTransformerOptions } from './transformers/filterByName';
|
||||||
|
export { ReduceTransformerOptions } from './transformers/reduce';
|
||||||
@@ -1,27 +1,16 @@
|
|||||||
import { Field, DataFrame } from '../../types/dataFrame';
|
|
||||||
import { Registry, RegistryItemWithOptions } from '../registry';
|
|
||||||
|
|
||||||
export type FieldMatcher = (field: Field) => boolean;
|
|
||||||
export type FrameMatcher = (frame: DataFrame) => boolean;
|
|
||||||
|
|
||||||
export interface FieldMatcherInfo<TOptions = any> extends RegistryItemWithOptions<TOptions> {
|
|
||||||
get: (options: TOptions) => FieldMatcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FrameMatcherInfo<TOptions = any> extends RegistryItemWithOptions<TOptions> {
|
|
||||||
get: (options: TOptions) => FrameMatcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MatcherConfig<TOptions = any> {
|
|
||||||
id: string;
|
|
||||||
options?: TOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the Buildtin matchers
|
// Load the Buildtin matchers
|
||||||
import { getFieldPredicateMatchers, getFramePredicateMatchers } from './predicates';
|
import { getFieldPredicateMatchers, getFramePredicateMatchers } from './matchers/predicates';
|
||||||
import { getFieldNameMatchers, getFrameNameMatchers } from './nameMatcher';
|
import { getFieldNameMatchers, getFrameNameMatchers } from './matchers/nameMatcher';
|
||||||
import { getFieldTypeMatchers } from './fieldTypeMatcher';
|
import { getFieldTypeMatchers } from './matchers/fieldTypeMatcher';
|
||||||
import { getRefIdMatchers } from './refIdMatcher';
|
import { getRefIdMatchers } from './matchers/refIdMatcher';
|
||||||
|
import {
|
||||||
|
FieldMatcherInfo,
|
||||||
|
MatcherConfig,
|
||||||
|
FrameMatcherInfo,
|
||||||
|
FieldMatcher,
|
||||||
|
FrameMatcher,
|
||||||
|
} from '../types/transformations';
|
||||||
|
import { Registry } from '../utils/Registry';
|
||||||
|
|
||||||
export const fieldMatchers = new Registry<FieldMatcherInfo>(() => {
|
export const fieldMatchers = new Registry<FieldMatcherInfo>(() => {
|
||||||
return [
|
return [
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { FieldType } from '../../types/dataFrame';
|
import { FieldType } from '../../types/dataFrame';
|
||||||
import { fieldMatchers } from './matchers';
|
import { fieldMatchers } from '../matchers';
|
||||||
import { FieldMatcherID } from './ids';
|
import { FieldMatcherID } from './ids';
|
||||||
import { toDataFrame } from '../processDataFrame';
|
import { toDataFrame } from '../../dataframe/processDataFrame';
|
||||||
|
|
||||||
export const simpleSeriesWithTypes = toDataFrame({
|
export const simpleSeriesWithTypes = toDataFrame({
|
||||||
fields: [
|
fields: [
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Field, FieldType } from '../../types/dataFrame';
|
import { Field, FieldType } from '../../types/dataFrame';
|
||||||
import { FieldMatcherInfo } from './matchers';
|
|
||||||
import { FieldMatcherID } from './ids';
|
import { FieldMatcherID } from './ids';
|
||||||
|
import { FieldMatcherInfo } from '../../types/transformations';
|
||||||
|
|
||||||
// General Field matcher
|
// General Field matcher
|
||||||
const fieldTypeMacher: FieldMatcherInfo<FieldType> = {
|
const fieldTypeMacher: FieldMatcherInfo<FieldType> = {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { fieldMatchers } from './matchers';
|
import { fieldMatchers } from '../matchers';
|
||||||
import { FieldMatcherID } from './ids';
|
import { FieldMatcherID } from './ids';
|
||||||
|
|
||||||
describe('Matchers', () => {
|
describe('Matchers', () => {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { getFieldMatcher } from './matchers';
|
import { getFieldMatcher } from '../matchers';
|
||||||
import { FieldMatcherID } from './ids';
|
import { FieldMatcherID } from './ids';
|
||||||
import { toDataFrame } from '../processDataFrame';
|
import { toDataFrame } from '../../dataframe/processDataFrame';
|
||||||
|
|
||||||
describe('Field Name Matcher', () => {
|
describe('Field Name Matcher', () => {
|
||||||
it('Match all with wildcard regex', () => {
|
it('Match all with wildcard regex', () => {
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Field, DataFrame } from '../../types/dataFrame';
|
import { Field, DataFrame } from '../../types/dataFrame';
|
||||||
import { FieldMatcherInfo, FrameMatcherInfo } from './matchers';
|
|
||||||
import { FieldMatcherID, FrameMatcherID } from './ids';
|
import { FieldMatcherID, FrameMatcherID } from './ids';
|
||||||
import { stringToJsRegex } from '../string';
|
import { FieldMatcherInfo, FrameMatcherInfo } from '../../types/transformations';
|
||||||
|
import { stringToJsRegex } from '../../text/string';
|
||||||
|
|
||||||
// General Field matcher
|
// General Field matcher
|
||||||
const fieldNameMacher: FieldMatcherInfo<string> = {
|
const fieldNameMacher: FieldMatcherInfo<string> = {
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { FieldType } from '../../types/dataFrame';
|
import { FieldType } from '../../types/dataFrame';
|
||||||
import { MatcherConfig, fieldMatchers } from './matchers';
|
import { fieldMatchers } from '../matchers';
|
||||||
import { simpleSeriesWithTypes } from './fieldTypeMatcher.test';
|
import { simpleSeriesWithTypes } from './fieldTypeMatcher.test';
|
||||||
import { FieldMatcherID, MatcherID } from './ids';
|
import { FieldMatcherID, MatcherID } from './ids';
|
||||||
|
import { MatcherConfig } from '../../types/transformations';
|
||||||
|
|
||||||
const matchesNumberConfig: MatcherConfig = {
|
const matchesNumberConfig: MatcherConfig = {
|
||||||
id: FieldMatcherID.byType,
|
id: FieldMatcherID.byType,
|
||||||
@@ -1,14 +1,7 @@
|
|||||||
import { Field, DataFrame } from '../../types/dataFrame';
|
import { Field, DataFrame } from '../../types/dataFrame';
|
||||||
import { MatcherID } from './ids';
|
import { MatcherID } from './ids';
|
||||||
import {
|
import { getFieldMatcher, fieldMatchers, getFrameMatchers, frameMatchers } from '../matchers';
|
||||||
FrameMatcherInfo,
|
import { FieldMatcherInfo, MatcherConfig, FrameMatcherInfo } from '../../types/transformations';
|
||||||
FieldMatcherInfo,
|
|
||||||
MatcherConfig,
|
|
||||||
getFieldMatcher,
|
|
||||||
fieldMatchers,
|
|
||||||
getFrameMatchers,
|
|
||||||
frameMatchers,
|
|
||||||
} from './matchers';
|
|
||||||
|
|
||||||
const anyFieldMatcher: FieldMatcherInfo<MatcherConfig[]> = {
|
const anyFieldMatcher: FieldMatcherInfo<MatcherConfig[]> = {
|
||||||
id: MatcherID.anyMatch,
|
id: MatcherID.anyMatch,
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { DataFrame } from '../../types/dataFrame';
|
import { DataFrame } from '../../types/dataFrame';
|
||||||
import { FrameMatcherInfo } from './matchers';
|
|
||||||
import { FrameMatcherID } from './ids';
|
import { FrameMatcherID } from './ids';
|
||||||
|
import { FrameMatcherInfo } from '../../types/transformations';
|
||||||
|
|
||||||
// General Field matcher
|
// General Field matcher
|
||||||
const refIdMacher: FrameMatcherInfo<string> = {
|
const refIdMacher: FrameMatcherInfo<string> = {
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import { DataTransformerID } from './ids';
|
import { DataTransformerID } from './transformers/ids';
|
||||||
import { dataTransformers } from './transformers';
|
import { transformersRegistry } from './transformers';
|
||||||
import { toDataFrame } from '../processDataFrame';
|
import { toDataFrame } from '../dataframe/processDataFrame';
|
||||||
import { ReducerID } from '../fieldReducer';
|
import { ReducerID } from './fieldReducer';
|
||||||
import { DataFrameView } from '../dataFrameView';
|
import { DataFrameView } from '../dataframe/DataFrameView';
|
||||||
|
|
||||||
describe('Transformers', () => {
|
describe('Transformers', () => {
|
||||||
it('should load all transformeres', () => {
|
it('should load all transformeres', () => {
|
||||||
for (const name of Object.keys(DataTransformerID)) {
|
for (const name of Object.keys(DataTransformerID)) {
|
||||||
const calc = dataTransformers.get(name);
|
const calc = transformersRegistry.get(name);
|
||||||
expect(calc.id).toBe(name);
|
expect(calc.id).toBe(name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -20,7 +20,7 @@ describe('Transformers', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should use fluent API', () => {
|
it('should use fluent API', () => {
|
||||||
const results = dataTransformers.reduce([seriesWithValues], {
|
const results = transformersRegistry.reduce([seriesWithValues], {
|
||||||
reducers: [ReducerID.first],
|
reducers: [ReducerID.first],
|
||||||
});
|
});
|
||||||
expect(results.length).toBe(1);
|
expect(results.length).toBe(1);
|
||||||
@@ -1,19 +1,13 @@
|
|||||||
import { DataFrame } from '../../types/dataFrame';
|
import { DataFrame } from '../types/dataFrame';
|
||||||
import { Registry, RegistryItemWithOptions } from '../registry';
|
import { Registry } from '../utils/Registry';
|
||||||
|
// Initalize the Registry
|
||||||
|
|
||||||
/**
|
import { appendTransformer, AppendOptions } from './transformers/append';
|
||||||
* Immutable data transformation
|
import { reduceTransformer, ReduceTransformerOptions } from './transformers/reduce';
|
||||||
*/
|
import { filterFieldsTransformer, filterFramesTransformer } from './transformers/filter';
|
||||||
export type DataTransformer = (data: DataFrame[]) => DataFrame[];
|
import { filterFieldsByNameTransformer, FilterFieldsByNameTransformerOptions } from './transformers/filterByName';
|
||||||
|
import { noopTransformer } from './transformers/noop';
|
||||||
export interface DataTransformerInfo<TOptions = any> extends RegistryItemWithOptions {
|
import { DataTransformerInfo, DataTransformerConfig } from '../types/transformations';
|
||||||
transformer: (options: TOptions) => DataTransformer;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DataTransformerConfig<TOptions = any> {
|
|
||||||
id: string;
|
|
||||||
options: TOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply configured transformations to the input data
|
* Apply configured transformations to the input data
|
||||||
@@ -21,7 +15,7 @@ export interface DataTransformerConfig<TOptions = any> {
|
|||||||
export function transformDataFrame(options: DataTransformerConfig[], data: DataFrame[]): DataFrame[] {
|
export function transformDataFrame(options: DataTransformerConfig[], data: DataFrame[]): DataFrame[] {
|
||||||
let processed = data;
|
let processed = data;
|
||||||
for (const config of options) {
|
for (const config of options) {
|
||||||
const info = dataTransformers.get(config.id);
|
const info = transformersRegistry.get(config.id);
|
||||||
const transformer = info.transformer(config.options);
|
const transformer = info.transformer(config.options);
|
||||||
const after = transformer(processed);
|
const after = transformer(processed);
|
||||||
|
|
||||||
@@ -43,14 +37,6 @@ export function transformDataFrame(options: DataTransformerConfig[], data: DataF
|
|||||||
return processed;
|
return processed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initalize the Registry
|
|
||||||
|
|
||||||
import { appendTransformer, AppendOptions } from './append';
|
|
||||||
import { reduceTransformer, ReduceTransformerOptions } from './reduce';
|
|
||||||
import { filterFieldsTransformer, filterFramesTransformer } from './filter';
|
|
||||||
import { filterFieldsByNameTransformer, FilterFieldsByNameTransformerOptions } from './filterByName';
|
|
||||||
import { noopTransformer } from './noop';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registry of transformation options that can be driven by
|
* Registry of transformation options that can be driven by
|
||||||
* stored configuration files.
|
* stored configuration files.
|
||||||
@@ -73,7 +59,7 @@ class TransformerRegistry extends Registry<DataTransformerInfo> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const dataTransformers = new TransformerRegistry(() => [
|
export const transformersRegistry = new TransformerRegistry(() => [
|
||||||
noopTransformer,
|
noopTransformer,
|
||||||
filterFieldsTransformer,
|
filterFieldsTransformer,
|
||||||
filterFieldsByNameTransformer,
|
filterFieldsByNameTransformer,
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { transformDataFrame, dataTransformers } from './transformers';
|
|
||||||
import { DataTransformerID } from './ids';
|
import { DataTransformerID } from './ids';
|
||||||
import { toDataFrame } from '../processDataFrame';
|
import { toDataFrame } from '../../dataframe/processDataFrame';
|
||||||
|
import { transformDataFrame } from '../transformers';
|
||||||
|
import { transformersRegistry } from '../transformers';
|
||||||
|
|
||||||
const seriesAB = toDataFrame({
|
const seriesAB = toDataFrame({
|
||||||
columns: [{ text: 'A' }, { text: 'B' }],
|
columns: [{ text: 'A' }, { text: 'B' }],
|
||||||
@@ -24,7 +25,7 @@ describe('Append Transformer', () => {
|
|||||||
id: DataTransformerID.append,
|
id: DataTransformerID.append,
|
||||||
options: {},
|
options: {},
|
||||||
};
|
};
|
||||||
const x = dataTransformers.get(DataTransformerID.append);
|
const x = transformersRegistry.get(DataTransformerID.append);
|
||||||
expect(x.id).toBe(cfg.id);
|
expect(x.id).toBe(cfg.id);
|
||||||
|
|
||||||
const processed = transformDataFrame([cfg], [seriesAB, seriesBC])[0];
|
const processed = transformDataFrame([cfg], [seriesAB, seriesBC])[0];
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { DataTransformerInfo } from './transformers';
|
|
||||||
import { DataFrame } from '../../types/dataFrame';
|
import { DataFrame } from '../../types/dataFrame';
|
||||||
import { DataTransformerID } from './ids';
|
import { DataTransformerID } from './ids';
|
||||||
import { MutableDataFrame } from '../dataFrameHelper';
|
import { MutableDataFrame } from '../../dataframe/MutableDataFrame';
|
||||||
|
import { DataTransformerInfo } from '../../types/transformations';
|
||||||
|
|
||||||
export interface AppendOptions {}
|
export interface AppendOptions {}
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { FieldType } from '../../types/dataFrame';
|
import { FieldType } from '../../types/dataFrame';
|
||||||
import { FieldMatcherID } from '../matchers/ids';
|
|
||||||
import { transformDataFrame } from './transformers';
|
|
||||||
import { DataTransformerID } from './ids';
|
import { DataTransformerID } from './ids';
|
||||||
import { toDataFrame } from '../processDataFrame';
|
import { toDataFrame } from '../../dataframe/processDataFrame';
|
||||||
|
import { FieldMatcherID } from '../matchers/ids';
|
||||||
|
import { transformDataFrame } from '../transformers';
|
||||||
|
|
||||||
export const simpleSeriesWithTypes = toDataFrame({
|
export const simpleSeriesWithTypes = toDataFrame({
|
||||||
fields: [
|
fields: [
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { DataTransformerInfo } from './transformers';
|
|
||||||
import { noopTransformer } from './noop';
|
import { noopTransformer } from './noop';
|
||||||
import { DataFrame, Field } from '../../types/dataFrame';
|
import { DataFrame, Field } from '../../types/dataFrame';
|
||||||
import { FieldMatcherID } from '../matchers/ids';
|
|
||||||
import { DataTransformerID } from './ids';
|
import { DataTransformerID } from './ids';
|
||||||
import { MatcherConfig, getFieldMatcher, getFrameMatchers } from '../matchers/matchers';
|
import { DataTransformerInfo, MatcherConfig } from '../../types/transformations';
|
||||||
|
import { FieldMatcherID } from '../matchers/ids';
|
||||||
|
import { getFieldMatcher, getFrameMatchers } from '../matchers';
|
||||||
|
|
||||||
export interface FilterOptions {
|
export interface FilterOptions {
|
||||||
include?: MatcherConfig;
|
include?: MatcherConfig;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { toDataFrame, transformDataFrame } from '../index';
|
|
||||||
import { FieldType } from '../../index';
|
|
||||||
import { DataTransformerID } from './ids';
|
import { DataTransformerID } from './ids';
|
||||||
|
import { transformDataFrame } from '../transformers';
|
||||||
|
import { toDataFrame } from '../../dataframe/processDataFrame';
|
||||||
|
import { FieldType } from '../../types/dataFrame';
|
||||||
|
|
||||||
export const seriesWithNamesToMatch = toDataFrame({
|
export const seriesWithNamesToMatch = toDataFrame({
|
||||||
fields: [
|
fields: [
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { DataTransformerInfo } from './transformers';
|
|
||||||
import { FieldMatcherID } from '../matchers/ids';
|
|
||||||
import { DataTransformerID } from './ids';
|
import { DataTransformerID } from './ids';
|
||||||
import { filterFieldsTransformer, FilterOptions } from './filter';
|
import { filterFieldsTransformer, FilterOptions } from './filter';
|
||||||
|
import { DataTransformerInfo } from '../../types/transformations';
|
||||||
|
import { FieldMatcherID } from '../matchers/ids';
|
||||||
|
|
||||||
export interface FilterFieldsByNameTransformerOptions {
|
export interface FilterFieldsByNameTransformerOptions {
|
||||||
include?: string;
|
include?: string;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { DataTransformerInfo } from './transformers';
|
|
||||||
import { DataTransformerID } from './ids';
|
import { DataTransformerID } from './ids';
|
||||||
import { DataFrame } from '../../types/dataFrame';
|
import { DataFrame } from '../../types/dataFrame';
|
||||||
|
import { DataTransformerInfo } from '../../types/transformations';
|
||||||
|
|
||||||
export interface NoopTransformerOptions {
|
export interface NoopTransformerOptions {
|
||||||
include?: string;
|
include?: string;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { transformDataFrame } from './transformers';
|
|
||||||
import { ReducerID } from '../fieldReducer';
|
import { ReducerID } from '../fieldReducer';
|
||||||
import { DataTransformerID } from './ids';
|
import { DataTransformerID } from './ids';
|
||||||
import { toDataFrame, toDataFrameDTO } from '../processDataFrame';
|
import { toDataFrame, toDataFrameDTO } from '../../dataframe/processDataFrame';
|
||||||
|
import { transformDataFrame } from '../transformers';
|
||||||
|
|
||||||
const seriesWithValues = toDataFrame({
|
const seriesWithValues = toDataFrame({
|
||||||
fields: [
|
fields: [
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import { DataTransformerInfo } from './transformers';
|
|
||||||
import { DataFrame, FieldType, Field } from '../../types/dataFrame';
|
|
||||||
import { MatcherConfig, getFieldMatcher } from '../matchers/matchers';
|
|
||||||
import { alwaysFieldMatcher } from '../matchers/predicates';
|
|
||||||
import { DataTransformerID } from './ids';
|
import { DataTransformerID } from './ids';
|
||||||
|
import { MatcherConfig, DataTransformerInfo } from '../../types/transformations';
|
||||||
import { ReducerID, fieldReducers, reduceField } from '../fieldReducer';
|
import { ReducerID, fieldReducers, reduceField } from '../fieldReducer';
|
||||||
|
import { alwaysFieldMatcher } from '../matchers/predicates';
|
||||||
|
import { DataFrame, Field, FieldType } from '../../types/dataFrame';
|
||||||
|
import { ArrayVector } from '../../vector/ArrayVector';
|
||||||
import { KeyValue } from '../../types/data';
|
import { KeyValue } from '../../types/data';
|
||||||
import { ArrayVector } from '../vector';
|
import { guessFieldTypeForField } from '../../dataframe/processDataFrame';
|
||||||
import { guessFieldTypeForField } from '../processDataFrame';
|
import { getFieldMatcher } from '../matchers';
|
||||||
|
|
||||||
export interface ReduceTransformerOptions {
|
export interface ReduceTransformerOptions {
|
||||||
reducers: ReducerID[];
|
reducers: ReducerID[];
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Threshold } from './threshold';
|
import { Threshold } from './threshold';
|
||||||
import { ValueMapping } from './valueMapping';
|
import { ValueMapping } from './valueMapping';
|
||||||
import { QueryResultBase, Labels, NullValueMode } from './data';
|
import { QueryResultBase, Labels, NullValueMode } from './data';
|
||||||
import { FieldCalcs } from '../utils/index';
|
|
||||||
import { DisplayProcessor } from './displayValue';
|
import { DisplayProcessor } from './displayValue';
|
||||||
import { DataLink } from './dataLink';
|
import { DataLink } from './dataLink';
|
||||||
|
import { Vector } from './vector';
|
||||||
|
import { FieldCalcs } from '../transformations/fieldReducer';
|
||||||
|
|
||||||
export enum FieldType {
|
export enum FieldType {
|
||||||
time = 'time', // or date
|
time = 'time', // or date
|
||||||
@@ -44,25 +45,6 @@ export interface FieldConfig {
|
|||||||
noValue?: string;
|
noValue?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Vector<T = any> {
|
|
||||||
length: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Access the value by index (Like an array)
|
|
||||||
*/
|
|
||||||
get(index: number): T;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the resutls as an array.
|
|
||||||
*/
|
|
||||||
toArray(): T[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the values as a simple array for json serialization
|
|
||||||
*/
|
|
||||||
toJSON(): any; // same results as toArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Field<T = any, V = Vector<T>> {
|
export interface Field<T = any, V = Vector<T>> {
|
||||||
name: string; // The column name
|
name: string; // The column name
|
||||||
type: FieldType;
|
type: FieldType;
|
||||||
|
|||||||
@@ -11,3 +11,4 @@ export * from './valueMapping';
|
|||||||
export * from './displayValue';
|
export * from './displayValue';
|
||||||
export * from './graph';
|
export * from './graph';
|
||||||
export * from './ScopedVars';
|
export * from './ScopedVars';
|
||||||
|
export * from './transformations';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DateTime } from '../utils/moment_wrapper';
|
import { DateTime } from '../datetime/moment_wrapper';
|
||||||
|
|
||||||
export interface RawTimeRange {
|
export interface RawTimeRange {
|
||||||
from: DateTime | string;
|
from: DateTime | string;
|
||||||
|
|||||||
32
packages/grafana-data/src/types/transformations.ts
Normal file
32
packages/grafana-data/src/types/transformations.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { DataFrame, Field } from './dataFrame';
|
||||||
|
import { RegistryItemWithOptions } from '../utils/Registry';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable data transformation
|
||||||
|
*/
|
||||||
|
export type DataTransformer = (data: DataFrame[]) => DataFrame[];
|
||||||
|
|
||||||
|
export interface DataTransformerInfo<TOptions = any> extends RegistryItemWithOptions {
|
||||||
|
transformer: (options: TOptions) => DataTransformer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DataTransformerConfig<TOptions = any> {
|
||||||
|
id: string;
|
||||||
|
options: TOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FieldMatcher = (field: Field) => boolean;
|
||||||
|
export type FrameMatcher = (frame: DataFrame) => boolean;
|
||||||
|
|
||||||
|
export interface FieldMatcherInfo<TOptions = any> extends RegistryItemWithOptions<TOptions> {
|
||||||
|
get: (options: TOptions) => FieldMatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FrameMatcherInfo<TOptions = any> extends RegistryItemWithOptions<TOptions> {
|
||||||
|
get: (options: TOptions) => FrameMatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MatcherConfig<TOptions = any> {
|
||||||
|
id: string;
|
||||||
|
options?: TOptions;
|
||||||
|
}
|
||||||
40
packages/grafana-data/src/types/vector.ts
Normal file
40
packages/grafana-data/src/types/vector.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
export interface Vector<T = any> {
|
||||||
|
length: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access the value by index (Like an array)
|
||||||
|
*/
|
||||||
|
get(index: number): T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the resutls as an array.
|
||||||
|
*/
|
||||||
|
toArray(): T[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the values as a simple array for json serialization
|
||||||
|
*/
|
||||||
|
toJSON(): any; // same results as toArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { readCSV, toCSV, CSVHeaderStyle } from './csv';
|
import { readCSV, toCSV, CSVHeaderStyle } from './csv';
|
||||||
import { getDataFrameRow } from './processDataFrame';
|
import { getDataFrameRow } from '../dataframe/processDataFrame';
|
||||||
|
|
||||||
// Test with local CSV files
|
// Test with local CSV files
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { toDataFrameDTO } from './processDataFrame';
|
import { toDataFrameDTO } from '../dataframe/processDataFrame';
|
||||||
|
|
||||||
describe('read csv', () => {
|
describe('read csv', () => {
|
||||||
it('should get X and y', () => {
|
it('should get X and y', () => {
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ 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 '../dataframe/processDataFrame';
|
||||||
import { MutableDataFrame } from './dataFrameHelper';
|
import { MutableDataFrame } from '../dataframe/MutableDataFrame';
|
||||||
|
|
||||||
export enum CSVHeaderStyle {
|
export enum CSVHeaderStyle {
|
||||||
full,
|
full,
|
||||||
|
|||||||
28
packages/grafana-data/src/utils/fieldParser.ts
Normal file
28
packages/grafana-data/src/utils/fieldParser.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { Field, FieldType } from '../types/dataFrame';
|
||||||
|
import { guessFieldTypeFromValue } from '../dataframe/processDataFrame';
|
||||||
|
|
||||||
|
export function makeFieldParser(value: any, field: Field): (value: string) => any {
|
||||||
|
if (!field.type) {
|
||||||
|
if (field.name === 'time' || field.name === 'Time') {
|
||||||
|
field.type = FieldType.time;
|
||||||
|
} else {
|
||||||
|
field.type = guessFieldTypeFromValue(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.type === FieldType.number) {
|
||||||
|
return (value: string) => {
|
||||||
|
return parseFloat(value);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Will convert anything that starts with "T" to true
|
||||||
|
if (field.type === FieldType.boolean) {
|
||||||
|
return (value: string) => {
|
||||||
|
return !(value[0] === 'F' || value[0] === 'f' || value[0] === '0');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just pass the string back
|
||||||
|
return (value: string) => value;
|
||||||
|
}
|
||||||
@@ -1,29 +1,10 @@
|
|||||||
export * from './string';
|
export * from './Registry';
|
||||||
export * from './registry';
|
|
||||||
export * from './markdown';
|
|
||||||
export * from './processDataFrame';
|
|
||||||
export * from './deprecationWarning';
|
export * from './deprecationWarning';
|
||||||
export * from './csv';
|
export * from './csv';
|
||||||
export * from './fieldReducer';
|
|
||||||
export * from './logs';
|
export * from './logs';
|
||||||
export * from './labels';
|
export * from './labels';
|
||||||
export * from './labels';
|
export * from './labels';
|
||||||
export * from './object';
|
export * from './object';
|
||||||
export * from './moment_wrapper';
|
|
||||||
export * from './thresholds';
|
export * from './thresholds';
|
||||||
export * from './text';
|
|
||||||
export * from './dataFrameHelper';
|
|
||||||
export * from './dataFrameView';
|
|
||||||
export * from './vector';
|
|
||||||
|
|
||||||
export { getMappedValue } from './valueMappings';
|
export { getMappedValue } from './valueMappings';
|
||||||
|
|
||||||
// Names are too general to export globally
|
|
||||||
import * as dateMath from './datemath';
|
|
||||||
import * as rangeUtil from './rangeutil';
|
|
||||||
export { dateMath, rangeUtil };
|
|
||||||
|
|
||||||
export * from './matchers/ids';
|
|
||||||
export * from './matchers/matchers';
|
|
||||||
export * from './transformers/ids';
|
|
||||||
export * from './transformers/transformers';
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { countBy, chain, map, escapeRegExp } from 'lodash';
|
|||||||
|
|
||||||
import { LogLevel, LogRowModel, LogLabelStatsModel, LogsParser } from '../types/logs';
|
import { LogLevel, LogRowModel, LogLabelStatsModel, LogsParser } from '../types/logs';
|
||||||
import { DataFrame, FieldType } from '../types/index';
|
import { DataFrame, FieldType } from '../types/index';
|
||||||
import { ArrayVector } from './vector';
|
import { ArrayVector } from '../vector/ArrayVector';
|
||||||
|
|
||||||
const LOGFMT_REGEXP = /(?:^|\s)(\w+)=("[^"]*"|\S+)/;
|
const LOGFMT_REGEXP = /(?:^|\s)(\w+)=("[^"]*"|\S+)/;
|
||||||
|
|
||||||
|
|||||||
@@ -1,338 +0,0 @@
|
|||||||
import { Vector } from '../types/dataFrame';
|
|
||||||
|
|
||||||
export function vectorToArray<T>(v: Vector<T>): T[] {
|
|
||||||
const arr: T[] = [];
|
|
||||||
for (let i = 0; i < v.length; i++) {
|
|
||||||
arr[i] = v.get(i);
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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[];
|
|
||||||
|
|
||||||
constructor(buffer?: T[]) {
|
|
||||||
this.buffer = buffer ? buffer : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
get length() {
|
|
||||||
return this.buffer.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
add(value: T) {
|
|
||||||
this.buffer.push(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
get(index: number): T {
|
|
||||||
return this.buffer[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
set(index: number, value: T) {
|
|
||||||
this.buffer[index] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
reverse() {
|
|
||||||
this.buffer.reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
toArray(): T[] {
|
|
||||||
return this.buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): T[] {
|
|
||||||
return this.buffer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ConstantVector<T = any> implements Vector<T> {
|
|
||||||
constructor(private value: T, private len: number) {}
|
|
||||||
|
|
||||||
get length() {
|
|
||||||
return this.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
get(index: number): T {
|
|
||||||
return this.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
toArray(): T[] {
|
|
||||||
const arr = new Array<T>(this.length);
|
|
||||||
return arr.fill(this.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): T[] {
|
|
||||||
return this.toArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ScaledVector implements Vector<number> {
|
|
||||||
constructor(private source: Vector<number>, private scale: number) {}
|
|
||||||
|
|
||||||
get length(): number {
|
|
||||||
return this.source.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
get(index: number): number {
|
|
||||||
return this.source.get(index) * this.scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
toArray(): number[] {
|
|
||||||
return vectorToArray(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): number[] {
|
|
||||||
return vectorToArray(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Values are returned in the order defined by the input parameter
|
|
||||||
*/
|
|
||||||
export class SortedVector<T = any> implements Vector<T> {
|
|
||||||
constructor(private source: Vector<T>, private order: number[]) {}
|
|
||||||
|
|
||||||
get length(): number {
|
|
||||||
return this.source.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
get(index: number): T {
|
|
||||||
return this.source.get(this.order[index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
toArray(): T[] {
|
|
||||||
return vectorToArray(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): T[] {
|
|
||||||
return vectorToArray(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CircularOptions<T> {
|
|
||||||
buffer?: T[];
|
|
||||||
append?: 'head' | 'tail';
|
|
||||||
capacity?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Circular vector uses a single buffer to capture a stream of values
|
|
||||||
* overwriting the oldest value on add.
|
|
||||||
*
|
|
||||||
* This supports addting to the 'head' or 'tail' and will grow the buffer
|
|
||||||
* to match a configured capacity.
|
|
||||||
*/
|
|
||||||
export class CircularVector<T = any> implements MutableVector<T> {
|
|
||||||
private buffer: T[];
|
|
||||||
private index: number;
|
|
||||||
private capacity: number;
|
|
||||||
private tail: boolean;
|
|
||||||
|
|
||||||
constructor(options: CircularOptions<T>) {
|
|
||||||
this.buffer = options.buffer || [];
|
|
||||||
this.capacity = this.buffer.length;
|
|
||||||
this.tail = 'head' !== options.append;
|
|
||||||
this.index = 0;
|
|
||||||
|
|
||||||
this.add = this.getAddFunction();
|
|
||||||
if (options.capacity) {
|
|
||||||
this.setCapacity(options.capacity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This gets the appropriate add function depending on the buffer state:
|
|
||||||
* * head vs tail
|
|
||||||
* * growing buffer vs overwriting values
|
|
||||||
*/
|
|
||||||
private getAddFunction() {
|
|
||||||
// When we are not at capacity, it should actually modify the buffer
|
|
||||||
if (this.capacity > this.buffer.length) {
|
|
||||||
if (this.tail) {
|
|
||||||
return (value: T) => {
|
|
||||||
this.buffer.push(value);
|
|
||||||
if (this.buffer.length >= this.capacity) {
|
|
||||||
this.add = this.getAddFunction();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return (value: T) => {
|
|
||||||
this.buffer.unshift(value);
|
|
||||||
if (this.buffer.length >= this.capacity) {
|
|
||||||
this.add = this.getAddFunction();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.tail) {
|
|
||||||
return (value: T) => {
|
|
||||||
this.buffer[this.index] = value;
|
|
||||||
this.index = (this.index + 1) % this.buffer.length;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append values to the head
|
|
||||||
return (value: T) => {
|
|
||||||
let idx = this.index - 1;
|
|
||||||
if (idx < 0) {
|
|
||||||
idx = this.buffer.length - 1;
|
|
||||||
}
|
|
||||||
this.buffer[idx] = value;
|
|
||||||
this.index = idx;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
setCapacity(v: number) {
|
|
||||||
if (this.capacity === v) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Make a copy so it is in order and new additions can be at the head or tail
|
|
||||||
const copy = this.toArray();
|
|
||||||
if (v > this.length) {
|
|
||||||
this.buffer = copy;
|
|
||||||
} else if (v < this.capacity) {
|
|
||||||
// Shrink the buffer
|
|
||||||
const delta = this.length - v;
|
|
||||||
if (this.tail) {
|
|
||||||
this.buffer = copy.slice(delta, copy.length); // Keep last items
|
|
||||||
} else {
|
|
||||||
this.buffer = copy.slice(0, copy.length - delta); // Keep first items
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.capacity = v;
|
|
||||||
this.index = 0;
|
|
||||||
this.add = this.getAddFunction();
|
|
||||||
}
|
|
||||||
|
|
||||||
setAppendMode(mode: 'head' | 'tail') {
|
|
||||||
const tail = 'head' !== mode;
|
|
||||||
if (tail !== this.tail) {
|
|
||||||
this.buffer = this.toArray().reverse();
|
|
||||||
this.index = 0;
|
|
||||||
this.tail = tail;
|
|
||||||
this.add = this.getAddFunction();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reverse() {
|
|
||||||
this.buffer.reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the value to the buffer
|
|
||||||
*/
|
|
||||||
add: (value: T) => void;
|
|
||||||
|
|
||||||
get(index: number) {
|
|
||||||
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() {
|
|
||||||
return this.buffer.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
toArray(): T[] {
|
|
||||||
return vectorToArray(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): T[] {
|
|
||||||
return vectorToArray(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AppendedVectorInfo<T> {
|
|
||||||
start: number;
|
|
||||||
end: number;
|
|
||||||
values: Vector<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This may be more trouble than it is worth. This trades some computation time for
|
|
||||||
* RAM -- rather than allocate a new array the size of all previous arrays, this just
|
|
||||||
* points the correct index to their original array values
|
|
||||||
*/
|
|
||||||
export class AppendedVectors<T = any> implements Vector<T> {
|
|
||||||
length = 0;
|
|
||||||
source: Array<AppendedVectorInfo<T>> = new Array<AppendedVectorInfo<T>>();
|
|
||||||
|
|
||||||
constructor(startAt = 0) {
|
|
||||||
this.length = startAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make the vector look like it is this long
|
|
||||||
*/
|
|
||||||
setLength(length: number) {
|
|
||||||
if (length > this.length) {
|
|
||||||
// make the vector longer (filling with undefined)
|
|
||||||
this.length = length;
|
|
||||||
} else if (length < this.length) {
|
|
||||||
// make the array shorter
|
|
||||||
const sources: Array<AppendedVectorInfo<T>> = new Array<AppendedVectorInfo<T>>();
|
|
||||||
for (const src of this.source) {
|
|
||||||
sources.push(src);
|
|
||||||
if (src.end > length) {
|
|
||||||
src.end = length;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.source = sources;
|
|
||||||
this.length = length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
append(v: Vector<T>): AppendedVectorInfo<T> {
|
|
||||||
const info = {
|
|
||||||
start: this.length,
|
|
||||||
end: this.length + v.length,
|
|
||||||
values: v,
|
|
||||||
};
|
|
||||||
this.length = info.end;
|
|
||||||
this.source.push(info);
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
get(index: number): T {
|
|
||||||
for (let i = 0; i < this.source.length; i++) {
|
|
||||||
const src = this.source[i];
|
|
||||||
if (index >= src.start && index < src.end) {
|
|
||||||
return src.values.get(index - src.start);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (undefined as unknown) as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
toArray(): T[] {
|
|
||||||
return vectorToArray(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): T[] {
|
|
||||||
return vectorToArray(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
23
packages/grafana-data/src/vector/AppendedVectors.test.ts
Normal file
23
packages/grafana-data/src/vector/AppendedVectors.test.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { ArrayVector } from './ArrayVector';
|
||||||
|
import { AppendedVectors } from './AppendedVectors';
|
||||||
|
|
||||||
|
describe('Check Appending Vector', () => {
|
||||||
|
it('should transparently join them', () => {
|
||||||
|
const appended = new AppendedVectors();
|
||||||
|
appended.append(new ArrayVector([1, 2, 3]));
|
||||||
|
appended.append(new ArrayVector([4, 5, 6]));
|
||||||
|
appended.append(new ArrayVector([7, 8, 9]));
|
||||||
|
expect(appended.length).toEqual(9);
|
||||||
|
|
||||||
|
appended.setLength(5);
|
||||||
|
expect(appended.length).toEqual(5);
|
||||||
|
appended.append(new ArrayVector(['a', 'b', 'c']));
|
||||||
|
expect(appended.length).toEqual(8);
|
||||||
|
expect(appended.toArray()).toEqual([1, 2, 3, 4, 5, 'a', 'b', 'c']);
|
||||||
|
|
||||||
|
appended.setLength(2);
|
||||||
|
appended.setLength(6);
|
||||||
|
appended.append(new ArrayVector(['x', 'y', 'z']));
|
||||||
|
expect(appended.toArray()).toEqual([1, 2, undefined, undefined, undefined, undefined, 'x', 'y', 'z']);
|
||||||
|
});
|
||||||
|
});
|
||||||
73
packages/grafana-data/src/vector/AppendedVectors.ts
Normal file
73
packages/grafana-data/src/vector/AppendedVectors.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { Vector } from '../types/vector';
|
||||||
|
import { vectorToArray } from './vectorToArray';
|
||||||
|
|
||||||
|
interface AppendedVectorInfo<T> {
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
values: Vector<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This may be more trouble than it is worth. This trades some computation time for
|
||||||
|
* RAM -- rather than allocate a new array the size of all previous arrays, this just
|
||||||
|
* points the correct index to their original array values
|
||||||
|
*/
|
||||||
|
export class AppendedVectors<T = any> implements Vector<T> {
|
||||||
|
length = 0;
|
||||||
|
source: Array<AppendedVectorInfo<T>> = new Array<AppendedVectorInfo<T>>();
|
||||||
|
|
||||||
|
constructor(startAt = 0) {
|
||||||
|
this.length = startAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make the vector look like it is this long
|
||||||
|
*/
|
||||||
|
setLength(length: number) {
|
||||||
|
if (length > this.length) {
|
||||||
|
// make the vector longer (filling with undefined)
|
||||||
|
this.length = length;
|
||||||
|
} else if (length < this.length) {
|
||||||
|
// make the array shorter
|
||||||
|
const sources: Array<AppendedVectorInfo<T>> = new Array<AppendedVectorInfo<T>>();
|
||||||
|
for (const src of this.source) {
|
||||||
|
sources.push(src);
|
||||||
|
if (src.end > length) {
|
||||||
|
src.end = length;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.source = sources;
|
||||||
|
this.length = length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
append(v: Vector<T>): AppendedVectorInfo<T> {
|
||||||
|
const info = {
|
||||||
|
start: this.length,
|
||||||
|
end: this.length + v.length,
|
||||||
|
values: v,
|
||||||
|
};
|
||||||
|
this.length = info.end;
|
||||||
|
this.source.push(info);
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(index: number): T {
|
||||||
|
for (let i = 0; i < this.source.length; i++) {
|
||||||
|
const src = this.source[i];
|
||||||
|
if (index >= src.start && index < src.end) {
|
||||||
|
return src.values.get(index - src.start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (undefined as unknown) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
toArray(): T[] {
|
||||||
|
return vectorToArray(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(): T[] {
|
||||||
|
return vectorToArray(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
37
packages/grafana-data/src/vector/ArrayVector.ts
Normal file
37
packages/grafana-data/src/vector/ArrayVector.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { MutableVector } from '../types/vector';
|
||||||
|
|
||||||
|
export class ArrayVector<T = any> implements MutableVector<T> {
|
||||||
|
buffer: T[];
|
||||||
|
|
||||||
|
constructor(buffer?: T[]) {
|
||||||
|
this.buffer = buffer ? buffer : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
get length() {
|
||||||
|
return this.buffer.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
add(value: T) {
|
||||||
|
this.buffer.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
get(index: number): T {
|
||||||
|
return this.buffer[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
set(index: number, value: T) {
|
||||||
|
this.buffer[index] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
reverse() {
|
||||||
|
this.buffer.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
toArray(): T[] {
|
||||||
|
return this.buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(): T[] {
|
||||||
|
return this.buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,31 +1,4 @@
|
|||||||
import { ConstantVector, ScaledVector, ArrayVector, CircularVector, AppendedVectors } from './vector';
|
import { CircularVector } from './CircularVector';
|
||||||
|
|
||||||
describe('Check Proxy Vector', () => {
|
|
||||||
it('should support constant values', () => {
|
|
||||||
const value = 3.5;
|
|
||||||
const v = new ConstantVector(value, 7);
|
|
||||||
expect(v.length).toEqual(7);
|
|
||||||
|
|
||||||
expect(v.get(0)).toEqual(value);
|
|
||||||
expect(v.get(1)).toEqual(value);
|
|
||||||
|
|
||||||
// Now check all of them
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
expect(v.get(i)).toEqual(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support multiply operations', () => {
|
|
||||||
const source = new ArrayVector([1, 2, 3, 4]);
|
|
||||||
const scale = 2.456;
|
|
||||||
const v = new ScaledVector(source, scale);
|
|
||||||
expect(v.length).toEqual(source.length);
|
|
||||||
// expect(v.push(10)).toEqual(source.length); // not implemented
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
expect(v.get(i)).toEqual(source.get(i) * scale);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Check Circular Vector', () => {
|
describe('Check Circular Vector', () => {
|
||||||
it('should append values', () => {
|
it('should append values', () => {
|
||||||
@@ -156,24 +129,3 @@ describe('Check Circular Vector', () => {
|
|||||||
expect(v.toArray()).toEqual([3, 4, 5]);
|
expect(v.toArray()).toEqual([3, 4, 5]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Check Appending Vector', () => {
|
|
||||||
it('should transparently join them', () => {
|
|
||||||
const appended = new AppendedVectors();
|
|
||||||
appended.append(new ArrayVector([1, 2, 3]));
|
|
||||||
appended.append(new ArrayVector([4, 5, 6]));
|
|
||||||
appended.append(new ArrayVector([7, 8, 9]));
|
|
||||||
expect(appended.length).toEqual(9);
|
|
||||||
|
|
||||||
appended.setLength(5);
|
|
||||||
expect(appended.length).toEqual(5);
|
|
||||||
appended.append(new ArrayVector(['a', 'b', 'c']));
|
|
||||||
expect(appended.length).toEqual(8);
|
|
||||||
expect(appended.toArray()).toEqual([1, 2, 3, 4, 5, 'a', 'b', 'c']);
|
|
||||||
|
|
||||||
appended.setLength(2);
|
|
||||||
appended.setLength(6);
|
|
||||||
appended.append(new ArrayVector(['x', 'y', 'z']));
|
|
||||||
expect(appended.toArray()).toEqual([1, 2, undefined, undefined, undefined, undefined, 'x', 'y', 'z']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
138
packages/grafana-data/src/vector/CircularVector.ts
Normal file
138
packages/grafana-data/src/vector/CircularVector.ts
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import { MutableVector } from '../types/vector';
|
||||||
|
import { vectorToArray } from './vectorToArray';
|
||||||
|
|
||||||
|
interface CircularOptions<T> {
|
||||||
|
buffer?: T[];
|
||||||
|
append?: 'head' | 'tail';
|
||||||
|
capacity?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Circular vector uses a single buffer to capture a stream of values
|
||||||
|
* overwriting the oldest value on add.
|
||||||
|
*
|
||||||
|
* This supports addting to the 'head' or 'tail' and will grow the buffer
|
||||||
|
* to match a configured capacity.
|
||||||
|
*/
|
||||||
|
export class CircularVector<T = any> implements MutableVector<T> {
|
||||||
|
private buffer: T[];
|
||||||
|
private index: number;
|
||||||
|
private capacity: number;
|
||||||
|
private tail: boolean;
|
||||||
|
|
||||||
|
constructor(options: CircularOptions<T>) {
|
||||||
|
this.buffer = options.buffer || [];
|
||||||
|
this.capacity = this.buffer.length;
|
||||||
|
this.tail = 'head' !== options.append;
|
||||||
|
this.index = 0;
|
||||||
|
|
||||||
|
this.add = this.getAddFunction();
|
||||||
|
if (options.capacity) {
|
||||||
|
this.setCapacity(options.capacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This gets the appropriate add function depending on the buffer state:
|
||||||
|
* * head vs tail
|
||||||
|
* * growing buffer vs overwriting values
|
||||||
|
*/
|
||||||
|
private getAddFunction() {
|
||||||
|
// When we are not at capacity, it should actually modify the buffer
|
||||||
|
if (this.capacity > this.buffer.length) {
|
||||||
|
if (this.tail) {
|
||||||
|
return (value: T) => {
|
||||||
|
this.buffer.push(value);
|
||||||
|
if (this.buffer.length >= this.capacity) {
|
||||||
|
this.add = this.getAddFunction();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return (value: T) => {
|
||||||
|
this.buffer.unshift(value);
|
||||||
|
if (this.buffer.length >= this.capacity) {
|
||||||
|
this.add = this.getAddFunction();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.tail) {
|
||||||
|
return (value: T) => {
|
||||||
|
this.buffer[this.index] = value;
|
||||||
|
this.index = (this.index + 1) % this.buffer.length;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append values to the head
|
||||||
|
return (value: T) => {
|
||||||
|
let idx = this.index - 1;
|
||||||
|
if (idx < 0) {
|
||||||
|
idx = this.buffer.length - 1;
|
||||||
|
}
|
||||||
|
this.buffer[idx] = value;
|
||||||
|
this.index = idx;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setCapacity(v: number) {
|
||||||
|
if (this.capacity === v) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Make a copy so it is in order and new additions can be at the head or tail
|
||||||
|
const copy = this.toArray();
|
||||||
|
if (v > this.length) {
|
||||||
|
this.buffer = copy;
|
||||||
|
} else if (v < this.capacity) {
|
||||||
|
// Shrink the buffer
|
||||||
|
const delta = this.length - v;
|
||||||
|
if (this.tail) {
|
||||||
|
this.buffer = copy.slice(delta, copy.length); // Keep last items
|
||||||
|
} else {
|
||||||
|
this.buffer = copy.slice(0, copy.length - delta); // Keep first items
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.capacity = v;
|
||||||
|
this.index = 0;
|
||||||
|
this.add = this.getAddFunction();
|
||||||
|
}
|
||||||
|
|
||||||
|
setAppendMode(mode: 'head' | 'tail') {
|
||||||
|
const tail = 'head' !== mode;
|
||||||
|
if (tail !== this.tail) {
|
||||||
|
this.buffer = this.toArray().reverse();
|
||||||
|
this.index = 0;
|
||||||
|
this.tail = tail;
|
||||||
|
this.add = this.getAddFunction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reverse() {
|
||||||
|
this.buffer.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the value to the buffer
|
||||||
|
*/
|
||||||
|
add: (value: T) => void;
|
||||||
|
|
||||||
|
get(index: number) {
|
||||||
|
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() {
|
||||||
|
return this.buffer.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
toArray(): T[] {
|
||||||
|
return vectorToArray(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(): T[] {
|
||||||
|
return vectorToArray(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
packages/grafana-data/src/vector/ConstantVector.test.ts
Normal file
17
packages/grafana-data/src/vector/ConstantVector.test.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { ConstantVector } from './ConstantVector';
|
||||||
|
|
||||||
|
describe('ConstantVector', () => {
|
||||||
|
it('should support constant values', () => {
|
||||||
|
const value = 3.5;
|
||||||
|
const v = new ConstantVector(value, 7);
|
||||||
|
expect(v.length).toEqual(7);
|
||||||
|
|
||||||
|
expect(v.get(0)).toEqual(value);
|
||||||
|
expect(v.get(1)).toEqual(value);
|
||||||
|
|
||||||
|
// Now check all of them
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
expect(v.get(i)).toEqual(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
22
packages/grafana-data/src/vector/ConstantVector.ts
Normal file
22
packages/grafana-data/src/vector/ConstantVector.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Vector } from '../types/vector';
|
||||||
|
|
||||||
|
export class ConstantVector<T = any> implements Vector<T> {
|
||||||
|
constructor(private value: T, private len: number) {}
|
||||||
|
|
||||||
|
get length() {
|
||||||
|
return this.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(index: number): T {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
toArray(): T[] {
|
||||||
|
const arr = new Array<T>(this.length);
|
||||||
|
return arr.fill(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(): T[] {
|
||||||
|
return this.toArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
15
packages/grafana-data/src/vector/ScaledVector.test.ts
Normal file
15
packages/grafana-data/src/vector/ScaledVector.test.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { ArrayVector } from './ArrayVector';
|
||||||
|
import { ScaledVector } from './ScaledVector';
|
||||||
|
|
||||||
|
describe('ScaledVector', () => {
|
||||||
|
it('should support multiply operations', () => {
|
||||||
|
const source = new ArrayVector([1, 2, 3, 4]);
|
||||||
|
const scale = 2.456;
|
||||||
|
const v = new ScaledVector(source, scale);
|
||||||
|
expect(v.length).toEqual(source.length);
|
||||||
|
// expect(v.push(10)).toEqual(source.length); // not implemented
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
expect(v.get(i)).toEqual(source.get(i) * scale);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
22
packages/grafana-data/src/vector/ScaledVector.ts
Normal file
22
packages/grafana-data/src/vector/ScaledVector.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Vector } from '../types/vector';
|
||||||
|
import { vectorToArray } from './vectorToArray';
|
||||||
|
|
||||||
|
export class ScaledVector implements Vector<number> {
|
||||||
|
constructor(private source: Vector<number>, private scale: number) {}
|
||||||
|
|
||||||
|
get length(): number {
|
||||||
|
return this.source.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(index: number): number {
|
||||||
|
return this.source.get(index) * this.scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
toArray(): number[] {
|
||||||
|
return vectorToArray(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(): number[] {
|
||||||
|
return vectorToArray(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
25
packages/grafana-data/src/vector/SortedVector.ts
Normal file
25
packages/grafana-data/src/vector/SortedVector.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { Vector } from '../types/vector';
|
||||||
|
import { vectorToArray } from './vectorToArray';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Values are returned in the order defined by the input parameter
|
||||||
|
*/
|
||||||
|
export class SortedVector<T = any> implements Vector<T> {
|
||||||
|
constructor(private source: Vector<T>, private order: number[]) {}
|
||||||
|
|
||||||
|
get length(): number {
|
||||||
|
return this.source.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(index: number): T {
|
||||||
|
return this.source.get(this.order[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
toArray(): T[] {
|
||||||
|
return vectorToArray(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(): T[] {
|
||||||
|
return vectorToArray(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
6
packages/grafana-data/src/vector/index.ts
Normal file
6
packages/grafana-data/src/vector/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export * from './AppendedVectors';
|
||||||
|
export * from './ArrayVector';
|
||||||
|
export * from './CircularVector';
|
||||||
|
export * from './ConstantVector';
|
||||||
|
export * from './ScaledVector';
|
||||||
|
export * from './SortedVector';
|
||||||
9
packages/grafana-data/src/vector/vectorToArray.ts
Normal file
9
packages/grafana-data/src/vector/vectorToArray.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { Vector } from '../types/vector';
|
||||||
|
|
||||||
|
export function vectorToArray<T>(v: Vector<T>): T[] {
|
||||||
|
const arr: T[] = [];
|
||||||
|
for (let i = 0; i < v.length; i++) {
|
||||||
|
arr[i] = v.get(i);
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { FilterFieldsByNameTransformerOptions, DataTransformerID, dataTransformers, KeyValue } from '@grafana/data';
|
import { FilterFieldsByNameTransformerOptions, DataTransformerID, transformersRegistry, KeyValue } from '@grafana/data';
|
||||||
import { TransformerUIProps, TransformerUIRegistyItem } from './types';
|
import { TransformerUIProps, TransformerUIRegistyItem } from './types';
|
||||||
import { ThemeContext } from '../../themes/ThemeContext';
|
import { ThemeContext } from '../../themes/ThemeContext';
|
||||||
import { css, cx } from 'emotion';
|
import { css, cx } from 'emotion';
|
||||||
@@ -157,7 +157,7 @@ const FilterPill: React.FC<FilterPillProps> = ({ label, selected, onClick }) =>
|
|||||||
export const filterFieldsByNameTransformRegistryItem: TransformerUIRegistyItem<FilterFieldsByNameTransformerOptions> = {
|
export const filterFieldsByNameTransformRegistryItem: TransformerUIRegistyItem<FilterFieldsByNameTransformerOptions> = {
|
||||||
id: DataTransformerID.filterFieldsByName,
|
id: DataTransformerID.filterFieldsByName,
|
||||||
component: FilterByNameTransformerEditor,
|
component: FilterByNameTransformerEditor,
|
||||||
transformer: dataTransformers.get(DataTransformerID.filterFieldsByName),
|
transformer: transformersRegistry.get(DataTransformerID.filterFieldsByName),
|
||||||
name: 'Filter by name',
|
name: 'Filter by name',
|
||||||
description: 'UI for filter by name transformation',
|
description: 'UI for filter by name transformation',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StatsPicker } from '../StatsPicker/StatsPicker';
|
import { StatsPicker } from '../StatsPicker/StatsPicker';
|
||||||
import { ReduceTransformerOptions, DataTransformerID, ReducerID } from '@grafana/data';
|
import { ReduceTransformerOptions, DataTransformerID, ReducerID, transformersRegistry } from '@grafana/data';
|
||||||
import { TransformerUIRegistyItem, TransformerUIProps } from './types';
|
import { TransformerUIRegistyItem, TransformerUIProps } from './types';
|
||||||
import { dataTransformers } from '@grafana/data';
|
|
||||||
|
|
||||||
// TODO: Minimal implementation, needs some <3
|
// TODO: Minimal implementation, needs some <3
|
||||||
export const ReduceTransformerEditor: React.FC<TransformerUIProps<ReduceTransformerOptions>> = ({
|
export const ReduceTransformerEditor: React.FC<TransformerUIProps<ReduceTransformerOptions>> = ({
|
||||||
@@ -29,7 +28,7 @@ export const ReduceTransformerEditor: React.FC<TransformerUIProps<ReduceTransfor
|
|||||||
export const reduceTransformRegistryItem: TransformerUIRegistyItem<ReduceTransformerOptions> = {
|
export const reduceTransformRegistryItem: TransformerUIRegistyItem<ReduceTransformerOptions> = {
|
||||||
id: DataTransformerID.reduce,
|
id: DataTransformerID.reduce,
|
||||||
component: ReduceTransformerEditor,
|
component: ReduceTransformerEditor,
|
||||||
transformer: dataTransformers.get(DataTransformerID.reduce),
|
transformer: transformersRegistry.get(DataTransformerID.reduce),
|
||||||
name: 'Reduce',
|
name: 'Reduce',
|
||||||
description: 'UI for reduce transformation',
|
description: 'UI for reduce transformation',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
jest.mock('@grafana/data/src/utils/moment_wrapper', () => ({
|
jest.mock('@grafana/data/src/datetime/moment_wrapper', () => ({
|
||||||
dateTime: (ts: any) => {
|
dateTime: (ts: any) => {
|
||||||
return {
|
return {
|
||||||
valueOf: () => ts,
|
valueOf: () => ts,
|
||||||
|
|||||||
Reference in New Issue
Block a user