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
pull/19192/head
Dominik Prokop 6 years ago committed by GitHub
parent 330cd597ec
commit 5c0f424d1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      packages/grafana-data/src/dataframe/CircularDataFrame.ts
  2. 8
      packages/grafana-data/src/dataframe/DataFrameView.test.ts
  3. 3
      packages/grafana-data/src/dataframe/DataFrameView.ts
  4. 94
      packages/grafana-data/src/dataframe/FieldCache.test.ts
  5. 78
      packages/grafana-data/src/dataframe/FieldCache.ts
  6. 66
      packages/grafana-data/src/dataframe/MutableDataFrame.test.ts
  7. 133
      packages/grafana-data/src/dataframe/MutableDataFrame.ts
  8. 5
      packages/grafana-data/src/dataframe/index.ts
  9. 4
      packages/grafana-data/src/dataframe/processDataFrame.test.ts
  10. 9
      packages/grafana-data/src/dataframe/processDataFrame.ts
  11. 2
      packages/grafana-data/src/datetime/datemath.test.ts
  12. 2
      packages/grafana-data/src/datetime/datemath.ts
  13. 5
      packages/grafana-data/src/datetime/index.ts
  14. 0
      packages/grafana-data/src/datetime/moment_wrapper.ts
  15. 2
      packages/grafana-data/src/datetime/rangeutil.ts
  16. 9
      packages/grafana-data/src/index.ts
  17. 3
      packages/grafana-data/src/text/index.ts
  18. 0
      packages/grafana-data/src/text/markdown.test.ts
  19. 0
      packages/grafana-data/src/text/markdown.ts
  20. 0
      packages/grafana-data/src/text/string.test.ts
  21. 0
      packages/grafana-data/src/text/string.ts
  22. 0
      packages/grafana-data/src/text/text.test.ts
  23. 0
      packages/grafana-data/src/text/text.ts
  24. 6
      packages/grafana-data/src/transformations/fieldReducer.test.ts
  25. 4
      packages/grafana-data/src/transformations/fieldReducer.ts
  26. 7
      packages/grafana-data/src/transformations/index.ts
  27. 35
      packages/grafana-data/src/transformations/matchers.ts
  28. 4
      packages/grafana-data/src/transformations/matchers/fieldTypeMatcher.test.ts
  29. 2
      packages/grafana-data/src/transformations/matchers/fieldTypeMatcher.ts
  30. 0
      packages/grafana-data/src/transformations/matchers/ids.ts
  31. 2
      packages/grafana-data/src/transformations/matchers/matchers.test.ts
  32. 4
      packages/grafana-data/src/transformations/matchers/nameMatcher.test.ts
  33. 4
      packages/grafana-data/src/transformations/matchers/nameMatcher.ts
  34. 3
      packages/grafana-data/src/transformations/matchers/predicates.test.ts
  35. 11
      packages/grafana-data/src/transformations/matchers/predicates.ts
  36. 2
      packages/grafana-data/src/transformations/matchers/refIdMatcher.ts
  37. 14
      packages/grafana-data/src/transformations/transformers.test.ts
  38. 36
      packages/grafana-data/src/transformations/transformers.ts
  39. 0
      packages/grafana-data/src/transformations/transformers/__snapshots__/reduce.test.ts.snap
  40. 7
      packages/grafana-data/src/transformations/transformers/append.test.ts
  41. 4
      packages/grafana-data/src/transformations/transformers/append.ts
  42. 6
      packages/grafana-data/src/transformations/transformers/filter.test.ts
  43. 6
      packages/grafana-data/src/transformations/transformers/filter.ts
  44. 5
      packages/grafana-data/src/transformations/transformers/filterByName.test.ts
  45. 4
      packages/grafana-data/src/transformations/transformers/filterByName.ts
  46. 0
      packages/grafana-data/src/transformations/transformers/ids.ts
  47. 2
      packages/grafana-data/src/transformations/transformers/noop.ts
  48. 4
      packages/grafana-data/src/transformations/transformers/reduce.test.ts
  49. 12
      packages/grafana-data/src/transformations/transformers/reduce.ts
  50. 22
      packages/grafana-data/src/types/dataFrame.ts
  51. 1
      packages/grafana-data/src/types/index.ts
  52. 2
      packages/grafana-data/src/types/time.ts
  53. 32
      packages/grafana-data/src/types/transformations.ts
  54. 40
      packages/grafana-data/src/types/vector.ts
  55. 0
      packages/grafana-data/src/utils/Registry.ts
  56. 4
      packages/grafana-data/src/utils/csv.test.ts
  57. 4
      packages/grafana-data/src/utils/csv.ts
  58. 28
      packages/grafana-data/src/utils/fieldParser.ts
  59. 21
      packages/grafana-data/src/utils/index.ts
  60. 2
      packages/grafana-data/src/utils/logs.ts
  61. 338
      packages/grafana-data/src/utils/vector.ts
  62. 23
      packages/grafana-data/src/vector/AppendedVectors.test.ts
  63. 73
      packages/grafana-data/src/vector/AppendedVectors.ts
  64. 37
      packages/grafana-data/src/vector/ArrayVector.ts
  65. 50
      packages/grafana-data/src/vector/CircularVector.test.ts
  66. 138
      packages/grafana-data/src/vector/CircularVector.ts
  67. 17
      packages/grafana-data/src/vector/ConstantVector.test.ts
  68. 22
      packages/grafana-data/src/vector/ConstantVector.ts
  69. 15
      packages/grafana-data/src/vector/ScaledVector.test.ts
  70. 22
      packages/grafana-data/src/vector/ScaledVector.ts
  71. 25
      packages/grafana-data/src/vector/SortedVector.ts
  72. 6
      packages/grafana-data/src/vector/index.ts
  73. 9
      packages/grafana-data/src/vector/vectorToArray.ts
  74. 4
      packages/grafana-ui/src/components/TransformersUI/FilterByNameTransformerEditor.tsx
  75. 5
      packages/grafana-ui/src/components/TransformersUI/ReduceTransformerEditor.tsx
  76. 2
      public/app/features/explore/utils/ResultProcessor.test.ts

@ -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 = {
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 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
]);
const ext = new FieldCache(frame);
// 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
]);
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]);
});
// 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);
it('should return index of the field', () => {
const field = ext.getFirstFieldOfType(FieldType.number);
expect(field!.index).toEqual(2);
});
});
});

@ -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];
}
}

@ -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,
});
});
}
}

@ -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'];

@ -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';

@ -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',

@ -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';
/**
* Immutable data transformation
*/
export type DataTransformer = (data: DataFrame[]) => DataFrame[];
export interface DataTransformerInfo<TOptions = any> extends RegistryItemWithOptions {
transformer: (options: TOptions) => DataTransformer;
}
import { DataFrame } from '../types/dataFrame';
import { Registry } from '../utils/Registry';
// Initalize the Registry
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;

@ -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;
}

@ -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,

@ -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);
}
}

@ -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']);
});
});

@ -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);
}
}

@ -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']);
});
});

@ -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);
}
}

@ -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);
}
});
});

@ -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();
}
}

@ -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);
}
});
});

@ -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);
}
}

@ -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);
}
}

@ -0,0 +1,6 @@
export * from './AppendedVectors';
export * from './ArrayVector';
export * from './CircularVector';
export * from './ConstantVector';
export * from './ScaledVector';
export * from './SortedVector';

@ -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 { FilterFieldsByNameTransformerOptions, DataTransformerID, dataTransformers, KeyValue } from '@grafana/data';
import { FilterFieldsByNameTransformerOptions, DataTransformerID, transformersRegistry, KeyValue } from '@grafana/data';
import { TransformerUIProps, TransformerUIRegistyItem } from './types';
import { ThemeContext } from '../../themes/ThemeContext';
import { css, cx } from 'emotion';
@ -157,7 +157,7 @@ const FilterPill: React.FC<FilterPillProps> = ({ label, selected, onClick }) =>
export const filterFieldsByNameTransformRegistryItem: TransformerUIRegistyItem<FilterFieldsByNameTransformerOptions> = {
id: DataTransformerID.filterFieldsByName,
component: FilterByNameTransformerEditor,
transformer: dataTransformers.get(DataTransformerID.filterFieldsByName),
transformer: transformersRegistry.get(DataTransformerID.filterFieldsByName),
name: 'Filter by name',
description: 'UI for filter by name transformation',
};

@ -1,8 +1,7 @@
import React from 'react';
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 { dataTransformers } from '@grafana/data';
// TODO: Minimal implementation, needs some <3
export const ReduceTransformerEditor: React.FC<TransformerUIProps<ReduceTransformerOptions>> = ({
@ -29,7 +28,7 @@ export const ReduceTransformerEditor: React.FC<TransformerUIProps<ReduceTransfor
export const reduceTransformRegistryItem: TransformerUIRegistyItem<ReduceTransformerOptions> = {
id: DataTransformerID.reduce,
component: ReduceTransformerEditor,
transformer: dataTransformers.get(DataTransformerID.reduce),
transformer: transformersRegistry.get(DataTransformerID.reduce),
name: 'Reduce',
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) => {
return {
valueOf: () => ts,

Loading…
Cancel
Save