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 { MutableDataFrame } from './dataFrameHelper';
|
||||
import { DataFrameView } from './dataFrameView';
|
||||
import { DateTime } from './moment_wrapper';
|
||||
import { FieldType, DataFrameDTO } from '../types/dataFrame';
|
||||
import { DateTime } from '../datetime/moment_wrapper';
|
||||
import { MutableDataFrame } from './MutableDataFrame';
|
||||
import { DataFrameView } from './DataFrameView';
|
||||
|
||||
interface MySpecialObject {
|
||||
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
|
@@ -1,30 +1,7 @@
|
||||
import { DataFrameDTO, FieldType } from '../types';
|
||||
import { FieldCache, MutableDataFrame } from './dataFrameHelper';
|
||||
import { FieldCache } from './FieldCache';
|
||||
import { FieldType } from '../types/dataFrame';
|
||||
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', () => {
|
||||
it('when creating a new FieldCache from fields should be able to query cache', () => {
|
||||
const frame = toDataFrame({
|
||||
@@ -90,68 +67,27 @@ describe('FieldCache', () => {
|
||||
expect(fieldCache.getFieldByName('undefined')!.name).toEqual(expectedFieldNames[5]);
|
||||
expect(fieldCache.getFieldByName('null')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('reverse', () => {
|
||||
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] },
|
||||
],
|
||||
};
|
||||
describe('field retrieval', () => {
|
||||
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);
|
||||
|
||||
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]);
|
||||
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]);
|
||||
it('should return index of the field', () => {
|
||||
const field = ext.getFirstFieldOfType(FieldType.number);
|
||||
expect(field!.index).toEqual(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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 { Labels, QueryResultMeta, KeyValue } from '../types/data';
|
||||
import { guessFieldTypeForField, guessFieldTypeFromValue, toDataFrameDTO } from './processDataFrame';
|
||||
import { ArrayVector, MutableVector, vectorToArray, CircularVector } from './vector';
|
||||
import { Field, DataFrame, DataFrameDTO, FieldDTO, FieldType } from '../types/dataFrame';
|
||||
import { KeyValue, QueryResultMeta, Labels } from '../types/data';
|
||||
import { guessFieldTypeFromValue, guessFieldTypeForField, toDataFrameDTO } from './processDataFrame';
|
||||
import isArray from 'lodash/isArray';
|
||||
import isString from 'lodash/isString';
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
import { makeFieldParser } from '../utils/fieldParser';
|
||||
import { MutableVector, Vector } from '../types/vector';
|
||||
import { ArrayVector } from '../vector/ArrayVector';
|
||||
import { vectorToArray } from '../vector/vectorToArray';
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
} from './processDataFrame';
|
||||
import { FieldType, TimeSeries, TableData, DataFrameDTO } from '../types/index';
|
||||
import { dateTime } from './moment_wrapper';
|
||||
import { MutableDataFrame } from './dataFrameHelper';
|
||||
import { dateTime } from '../datetime/moment_wrapper';
|
||||
import { MutableDataFrame } from './MutableDataFrame';
|
||||
|
||||
describe('toDataFrame', () => {
|
||||
it('converts timeseries to series', () => {
|
@@ -17,10 +17,11 @@ import {
|
||||
FieldDTO,
|
||||
DataFrameDTO,
|
||||
} from '../types/index';
|
||||
import { isDateTime } from './moment_wrapper';
|
||||
import { ArrayVector, SortedVector } from './vector';
|
||||
import { MutableDataFrame } from './dataFrameHelper';
|
||||
import { deprecationWarning } from './deprecationWarning';
|
||||
import { isDateTime } from '../datetime/moment_wrapper';
|
||||
import { deprecationWarning } from '../utils/deprecationWarning';
|
||||
import { ArrayVector } from '../vector/ArrayVector';
|
||||
import { MutableDataFrame } from './MutableDataFrame';
|
||||
import { SortedVector } from '../vector/SortedVector';
|
||||
|
||||
function convertTableToDataFrame(table: TableData): DataFrame {
|
||||
const fields = table.columns.map(c => {
|
@@ -2,7 +2,7 @@ import sinon, { SinonFakeTimers } from 'sinon';
|
||||
import each from 'lodash/each';
|
||||
|
||||
import * as dateMath from './datemath';
|
||||
import { dateTime, DurationUnit, DateTime } from '../utils/moment_wrapper';
|
||||
import { dateTime, DurationUnit, DateTime } from './moment_wrapper';
|
||||
|
||||
describe('DateMath', () => {
|
||||
const spans: DurationUnit[] = ['s', 'm', 'h', 'd', 'w', 'M', 'y'];
|
@@ -1,7 +1,7 @@
|
||||
import includes from 'lodash/includes';
|
||||
import isDate from 'lodash/isDate';
|
||||
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'];
|
||||
|
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 * 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 } } = {
|
||||
s: { display: 'second' },
|
@@ -1,2 +1,7 @@
|
||||
export * from './utils/index';
|
||||
export * from './types/index';
|
||||
export * from './utils';
|
||||
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 { Field, FieldType } from '../types/index';
|
||||
import { MutableDataFrame } from './dataFrameHelper';
|
||||
import { ArrayVector } from './vector';
|
||||
import { guessFieldTypeFromValue } from './processDataFrame';
|
||||
import { guessFieldTypeFromValue } from '../dataframe/processDataFrame';
|
||||
import { MutableDataFrame } from '../dataframe/MutableDataFrame';
|
||||
import { ArrayVector } from '../vector/ArrayVector';
|
||||
|
||||
/**
|
||||
* Run a reducer and get back the value
|
@@ -1,8 +1,8 @@
|
||||
// Libraries
|
||||
import isNumber from 'lodash/isNumber';
|
||||
|
||||
import { NullValueMode, Field } from '../types';
|
||||
import { Registry, RegistryItem } from './registry';
|
||||
import { NullValueMode, Field } from '../types/index';
|
||||
import { Registry, RegistryItem } from '../utils/Registry';
|
||||
|
||||
export enum ReducerID {
|
||||
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
|
||||
import { getFieldPredicateMatchers, getFramePredicateMatchers } from './predicates';
|
||||
import { getFieldNameMatchers, getFrameNameMatchers } from './nameMatcher';
|
||||
import { getFieldTypeMatchers } from './fieldTypeMatcher';
|
||||
import { getRefIdMatchers } from './refIdMatcher';
|
||||
import { getFieldPredicateMatchers, getFramePredicateMatchers } from './matchers/predicates';
|
||||
import { getFieldNameMatchers, getFrameNameMatchers } from './matchers/nameMatcher';
|
||||
import { getFieldTypeMatchers } from './matchers/fieldTypeMatcher';
|
||||
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>(() => {
|
||||
return [
|
@@ -1,7 +1,7 @@
|
||||
import { FieldType } from '../../types/dataFrame';
|
||||
import { fieldMatchers } from './matchers';
|
||||
import { fieldMatchers } from '../matchers';
|
||||
import { FieldMatcherID } from './ids';
|
||||
import { toDataFrame } from '../processDataFrame';
|
||||
import { toDataFrame } from '../../dataframe/processDataFrame';
|
||||
|
||||
export const simpleSeriesWithTypes = toDataFrame({
|
||||
fields: [
|
@@ -1,6 +1,6 @@
|
||||
import { Field, FieldType } from '../../types/dataFrame';
|
||||
import { FieldMatcherInfo } from './matchers';
|
||||
import { FieldMatcherID } from './ids';
|
||||
import { FieldMatcherInfo } from '../../types/transformations';
|
||||
|
||||
// General Field matcher
|
||||
const fieldTypeMacher: FieldMatcherInfo<FieldType> = {
|
@@ -1,4 +1,4 @@
|
||||
import { fieldMatchers } from './matchers';
|
||||
import { fieldMatchers } from '../matchers';
|
||||
import { FieldMatcherID } from './ids';
|
||||
|
||||
describe('Matchers', () => {
|
@@ -1,6 +1,6 @@
|
||||
import { getFieldMatcher } from './matchers';
|
||||
import { getFieldMatcher } from '../matchers';
|
||||
import { FieldMatcherID } from './ids';
|
||||
import { toDataFrame } from '../processDataFrame';
|
||||
import { toDataFrame } from '../../dataframe/processDataFrame';
|
||||
|
||||
describe('Field Name Matcher', () => {
|
||||
it('Match all with wildcard regex', () => {
|
@@ -1,7 +1,7 @@
|
||||
import { Field, DataFrame } from '../../types/dataFrame';
|
||||
import { FieldMatcherInfo, FrameMatcherInfo } from './matchers';
|
||||
import { FieldMatcherID, FrameMatcherID } from './ids';
|
||||
import { stringToJsRegex } from '../string';
|
||||
import { FieldMatcherInfo, FrameMatcherInfo } from '../../types/transformations';
|
||||
import { stringToJsRegex } from '../../text/string';
|
||||
|
||||
// General Field matcher
|
||||
const fieldNameMacher: FieldMatcherInfo<string> = {
|
@@ -1,7 +1,8 @@
|
||||
import { FieldType } from '../../types/dataFrame';
|
||||
import { MatcherConfig, fieldMatchers } from './matchers';
|
||||
import { fieldMatchers } from '../matchers';
|
||||
import { simpleSeriesWithTypes } from './fieldTypeMatcher.test';
|
||||
import { FieldMatcherID, MatcherID } from './ids';
|
||||
import { MatcherConfig } from '../../types/transformations';
|
||||
|
||||
const matchesNumberConfig: MatcherConfig = {
|
||||
id: FieldMatcherID.byType,
|
@@ -1,14 +1,7 @@
|
||||
import { Field, DataFrame } from '../../types/dataFrame';
|
||||
import { MatcherID } from './ids';
|
||||
import {
|
||||
FrameMatcherInfo,
|
||||
FieldMatcherInfo,
|
||||
MatcherConfig,
|
||||
getFieldMatcher,
|
||||
fieldMatchers,
|
||||
getFrameMatchers,
|
||||
frameMatchers,
|
||||
} from './matchers';
|
||||
import { getFieldMatcher, fieldMatchers, getFrameMatchers, frameMatchers } from '../matchers';
|
||||
import { FieldMatcherInfo, MatcherConfig, FrameMatcherInfo } from '../../types/transformations';
|
||||
|
||||
const anyFieldMatcher: FieldMatcherInfo<MatcherConfig[]> = {
|
||||
id: MatcherID.anyMatch,
|
@@ -1,6 +1,6 @@
|
||||
import { DataFrame } from '../../types/dataFrame';
|
||||
import { FrameMatcherInfo } from './matchers';
|
||||
import { FrameMatcherID } from './ids';
|
||||
import { FrameMatcherInfo } from '../../types/transformations';
|
||||
|
||||
// General Field matcher
|
||||
const refIdMacher: FrameMatcherInfo<string> = {
|
@@ -1,13 +1,13 @@
|
||||
import { DataTransformerID } from './ids';
|
||||
import { dataTransformers } from './transformers';
|
||||
import { toDataFrame } from '../processDataFrame';
|
||||
import { ReducerID } from '../fieldReducer';
|
||||
import { DataFrameView } from '../dataFrameView';
|
||||
import { DataTransformerID } from './transformers/ids';
|
||||
import { transformersRegistry } from './transformers';
|
||||
import { toDataFrame } from '../dataframe/processDataFrame';
|
||||
import { ReducerID } from './fieldReducer';
|
||||
import { DataFrameView } from '../dataframe/DataFrameView';
|
||||
|
||||
describe('Transformers', () => {
|
||||
it('should load all transformeres', () => {
|
||||
for (const name of Object.keys(DataTransformerID)) {
|
||||
const calc = dataTransformers.get(name);
|
||||
const calc = transformersRegistry.get(name);
|
||||
expect(calc.id).toBe(name);
|
||||
}
|
||||
});
|
||||
@@ -20,7 +20,7 @@ describe('Transformers', () => {
|
||||
});
|
||||
|
||||
it('should use fluent API', () => {
|
||||
const results = dataTransformers.reduce([seriesWithValues], {
|
||||
const results = transformersRegistry.reduce([seriesWithValues], {
|
||||
reducers: [ReducerID.first],
|
||||
});
|
||||
expect(results.length).toBe(1);
|
@@ -1,19 +1,13 @@
|
||||
import { DataFrame } from '../../types/dataFrame';
|
||||
import { Registry, RegistryItemWithOptions } from '../registry';
|
||||
import { DataFrame } from '../types/dataFrame';
|
||||
import { Registry } from '../utils/Registry';
|
||||
// Initalize the 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;
|
||||
}
|
||||
import { appendTransformer, AppendOptions } from './transformers/append';
|
||||
import { reduceTransformer, ReduceTransformerOptions } from './transformers/reduce';
|
||||
import { filterFieldsTransformer, filterFramesTransformer } from './transformers/filter';
|
||||
import { filterFieldsByNameTransformer, FilterFieldsByNameTransformerOptions } from './transformers/filterByName';
|
||||
import { noopTransformer } from './transformers/noop';
|
||||
import { DataTransformerInfo, DataTransformerConfig } from '../types/transformations';
|
||||
|
||||
/**
|
||||
* Apply configured transformations to the input data
|
||||
@@ -21,7 +15,7 @@ export interface DataTransformerConfig<TOptions = any> {
|
||||
export function transformDataFrame(options: DataTransformerConfig[], data: DataFrame[]): DataFrame[] {
|
||||
let processed = data;
|
||||
for (const config of options) {
|
||||
const info = dataTransformers.get(config.id);
|
||||
const info = transformersRegistry.get(config.id);
|
||||
const transformer = info.transformer(config.options);
|
||||
const after = transformer(processed);
|
||||
|
||||
@@ -43,14 +37,6 @@ export function transformDataFrame(options: DataTransformerConfig[], data: DataF
|
||||
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
|
||||
* stored configuration files.
|
||||
@@ -73,7 +59,7 @@ class TransformerRegistry extends Registry<DataTransformerInfo> {
|
||||
}
|
||||
}
|
||||
|
||||
export const dataTransformers = new TransformerRegistry(() => [
|
||||
export const transformersRegistry = new TransformerRegistry(() => [
|
||||
noopTransformer,
|
||||
filterFieldsTransformer,
|
||||
filterFieldsByNameTransformer,
|
@@ -1,6 +1,7 @@
|
||||
import { transformDataFrame, dataTransformers } from './transformers';
|
||||
import { DataTransformerID } from './ids';
|
||||
import { toDataFrame } from '../processDataFrame';
|
||||
import { toDataFrame } from '../../dataframe/processDataFrame';
|
||||
import { transformDataFrame } from '../transformers';
|
||||
import { transformersRegistry } from '../transformers';
|
||||
|
||||
const seriesAB = toDataFrame({
|
||||
columns: [{ text: 'A' }, { text: 'B' }],
|
||||
@@ -24,7 +25,7 @@ describe('Append Transformer', () => {
|
||||
id: DataTransformerID.append,
|
||||
options: {},
|
||||
};
|
||||
const x = dataTransformers.get(DataTransformerID.append);
|
||||
const x = transformersRegistry.get(DataTransformerID.append);
|
||||
expect(x.id).toBe(cfg.id);
|
||||
|
||||
const processed = transformDataFrame([cfg], [seriesAB, seriesBC])[0];
|
@@ -1,7 +1,7 @@
|
||||
import { DataTransformerInfo } from './transformers';
|
||||
import { DataFrame } from '../../types/dataFrame';
|
||||
import { DataTransformerID } from './ids';
|
||||
import { MutableDataFrame } from '../dataFrameHelper';
|
||||
import { MutableDataFrame } from '../../dataframe/MutableDataFrame';
|
||||
import { DataTransformerInfo } from '../../types/transformations';
|
||||
|
||||
export interface AppendOptions {}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { FieldType } from '../../types/dataFrame';
|
||||
import { FieldMatcherID } from '../matchers/ids';
|
||||
import { transformDataFrame } from './transformers';
|
||||
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({
|
||||
fields: [
|
@@ -1,9 +1,9 @@
|
||||
import { DataTransformerInfo } from './transformers';
|
||||
import { noopTransformer } from './noop';
|
||||
import { DataFrame, Field } from '../../types/dataFrame';
|
||||
import { FieldMatcherID } from '../matchers/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 {
|
||||
include?: MatcherConfig;
|
@@ -1,6 +1,7 @@
|
||||
import { toDataFrame, transformDataFrame } from '../index';
|
||||
import { FieldType } from '../../index';
|
||||
import { DataTransformerID } from './ids';
|
||||
import { transformDataFrame } from '../transformers';
|
||||
import { toDataFrame } from '../../dataframe/processDataFrame';
|
||||
import { FieldType } from '../../types/dataFrame';
|
||||
|
||||
export const seriesWithNamesToMatch = toDataFrame({
|
||||
fields: [
|
@@ -1,7 +1,7 @@
|
||||
import { DataTransformerInfo } from './transformers';
|
||||
import { FieldMatcherID } from '../matchers/ids';
|
||||
import { DataTransformerID } from './ids';
|
||||
import { filterFieldsTransformer, FilterOptions } from './filter';
|
||||
import { DataTransformerInfo } from '../../types/transformations';
|
||||
import { FieldMatcherID } from '../matchers/ids';
|
||||
|
||||
export interface FilterFieldsByNameTransformerOptions {
|
||||
include?: string;
|
@@ -1,6 +1,6 @@
|
||||
import { DataTransformerInfo } from './transformers';
|
||||
import { DataTransformerID } from './ids';
|
||||
import { DataFrame } from '../../types/dataFrame';
|
||||
import { DataTransformerInfo } from '../../types/transformations';
|
||||
|
||||
export interface NoopTransformerOptions {
|
||||
include?: string;
|
@@ -1,7 +1,7 @@
|
||||
import { transformDataFrame } from './transformers';
|
||||
import { ReducerID } from '../fieldReducer';
|
||||
import { DataTransformerID } from './ids';
|
||||
import { toDataFrame, toDataFrameDTO } from '../processDataFrame';
|
||||
import { toDataFrame, toDataFrameDTO } from '../../dataframe/processDataFrame';
|
||||
import { transformDataFrame } from '../transformers';
|
||||
|
||||
const seriesWithValues = toDataFrame({
|
||||
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 { MatcherConfig, DataTransformerInfo } from '../../types/transformations';
|
||||
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 { ArrayVector } from '../vector';
|
||||
import { guessFieldTypeForField } from '../processDataFrame';
|
||||
import { guessFieldTypeForField } from '../../dataframe/processDataFrame';
|
||||
import { getFieldMatcher } from '../matchers';
|
||||
|
||||
export interface ReduceTransformerOptions {
|
||||
reducers: ReducerID[];
|
@@ -1,9 +1,10 @@
|
||||
import { Threshold } from './threshold';
|
||||
import { ValueMapping } from './valueMapping';
|
||||
import { QueryResultBase, Labels, NullValueMode } from './data';
|
||||
import { FieldCalcs } from '../utils/index';
|
||||
import { DisplayProcessor } from './displayValue';
|
||||
import { DataLink } from './dataLink';
|
||||
import { Vector } from './vector';
|
||||
import { FieldCalcs } from '../transformations/fieldReducer';
|
||||
|
||||
export enum FieldType {
|
||||
time = 'time', // or date
|
||||
@@ -44,25 +45,6 @@ export interface FieldConfig {
|
||||
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>> {
|
||||
name: string; // The column name
|
||||
type: FieldType;
|
||||
|
@@ -11,3 +11,4 @@ export * from './valueMapping';
|
||||
export * from './displayValue';
|
||||
export * from './graph';
|
||||
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 {
|
||||
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 { getDataFrameRow } from './processDataFrame';
|
||||
import { getDataFrameRow } from '../dataframe/processDataFrame';
|
||||
|
||||
// Test with local CSV files
|
||||
import fs from 'fs';
|
||||
import { toDataFrameDTO } from './processDataFrame';
|
||||
import { toDataFrameDTO } from '../dataframe/processDataFrame';
|
||||
|
||||
describe('read csv', () => {
|
||||
it('should get X and y', () => {
|
||||
|
@@ -5,8 +5,8 @@ import isNumber from 'lodash/isNumber';
|
||||
|
||||
// Types
|
||||
import { DataFrame, Field, FieldType, FieldConfig } from '../types';
|
||||
import { guessFieldTypeFromValue } from './processDataFrame';
|
||||
import { MutableDataFrame } from './dataFrameHelper';
|
||||
import { guessFieldTypeFromValue } from '../dataframe/processDataFrame';
|
||||
import { MutableDataFrame } from '../dataframe/MutableDataFrame';
|
||||
|
||||
export enum CSVHeaderStyle {
|
||||
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 './markdown';
|
||||
export * from './processDataFrame';
|
||||
export * from './Registry';
|
||||
export * from './deprecationWarning';
|
||||
export * from './csv';
|
||||
export * from './fieldReducer';
|
||||
export * from './logs';
|
||||
export * from './labels';
|
||||
export * from './labels';
|
||||
export * from './object';
|
||||
export * from './moment_wrapper';
|
||||
export * from './thresholds';
|
||||
export * from './text';
|
||||
export * from './dataFrameHelper';
|
||||
export * from './dataFrameView';
|
||||
export * from './vector';
|
||||
|
||||
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 { DataFrame, FieldType } from '../types/index';
|
||||
import { ArrayVector } from './vector';
|
||||
import { ArrayVector } from '../vector/ArrayVector';
|
||||
|
||||
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';
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
import { CircularVector } from './CircularVector';
|
||||
|
||||
describe('Check Circular Vector', () => {
|
||||
it('should append values', () => {
|
||||
@@ -156,24 +129,3 @@ describe('Check Circular Vector', () => {
|
||||
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;
|
||||
}
|
Reference in New Issue
Block a user