From b8188eead4f2c2f415240b2107927dbee22e04a6 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Fri, 14 Apr 2023 05:36:53 -0700 Subject: [PATCH] FieldValues: Use plain arrays instead of Vector (part 1 of 2) (#66187) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Leon Sorokin Co-authored-by: Torkel Ödegaard --- .betterer.results | 34 +++- .../src/dataframe/ArrayDataFrame.ts | 8 +- .../src/dataframe/DataFrameJSON.test.ts | 2 +- .../src/dataframe/MutableDataFrame.ts | 34 ++-- .../transformers/joinDataFrames.test.ts | 2 +- .../transformers/reduce.test.ts | 6 +- packages/grafana-data/src/types/dataFrame.ts | 8 +- packages/grafana-data/src/types/vector.ts | 94 ++++++++-- .../src/vector/AppendedVectors.ts | 4 +- .../src/vector/ArrayVector.test.ts | 41 +++++ .../grafana-data/src/vector/ArrayVector.ts | 61 +++---- .../src/vector/BinaryOperationVector.ts | 7 +- .../src/vector/CircularVector.test.ts | 3 + .../grafana-data/src/vector/CircularVector.ts | 24 +-- .../grafana-data/src/vector/ConstantVector.ts | 8 +- .../src/vector/FunctionalVector.ts | 165 ++++++++++++++++-- .../grafana-data/src/vector/IndexVector.ts | 2 + packages/grafana-data/src/vector/RowVector.ts | 7 +- .../grafana-data/src/vector/SortedVector.ts | 9 +- .../grafana-data/src/vector/vectorToArray.ts | 1 + .../GraphNG/nullInsertThreshold.test.ts | 74 +++----- .../components/GraphNG/nullToValue.test.ts | 24 +-- public/app/features/geo/format/utils.ts | 4 +- .../app/features/geo/gazetteer/gazetteer.ts | 4 +- .../features/geo/utils/frameVectorSource.ts | 2 +- public/app/features/geo/utils/location.ts | 2 +- .../live/centrifuge/LiveDataStream.test.ts | 16 +- .../live/data/StreamingDataFrame.test.ts | 18 +- .../features/live/data/StreamingDataFrame.ts | 34 ++-- public/app/features/live/live.test.ts | 12 +- .../lookupGazetteer/fieldLookup.ts | 5 +- .../timeSeriesTableTransformer.test.ts | 22 +-- .../timeSeriesTableTransformer.ts | 8 +- .../elasticsearch/ElasticResponse.test.ts | 42 ++--- .../plugins/datasource/loki/responseUtils.ts | 2 +- .../datasource/postgres/datasource.test.ts | 20 +-- public/app/plugins/panel/timeseries/utils.ts | 14 +- 37 files changed, 531 insertions(+), 292 deletions(-) create mode 100644 packages/grafana-data/src/vector/ArrayVector.test.ts diff --git a/.betterer.results b/.betterer.results index ab21280defc..1166a7948a6 100644 --- a/.betterer.results +++ b/.betterer.results @@ -285,6 +285,9 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "2"], [0, 0, 0, "Unexpected any. Specify a different type.", "3"] ], + "packages/grafana-data/src/transformations/transformers/joinDataFrames.test.ts:5381": [ + [0, 0, 0, "Unexpected any. Specify a different type.", "0"] + ], "packages/grafana-data/src/transformations/transformers/merge.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -484,8 +487,17 @@ exports[`better eslint`] = { ], "packages/grafana-data/src/types/vector.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"] + [0, 0, 0, "Do not use any type assertions.", "1"], + [0, 0, 0, "Unexpected any. Specify a different type.", "2"], + [0, 0, 0, "Unexpected any. Specify a different type.", "3"], + [0, 0, 0, "Do not use any type assertions.", "4"], + [0, 0, 0, "Unexpected any. Specify a different type.", "5"], + [0, 0, 0, "Unexpected any. Specify a different type.", "6"], + [0, 0, 0, "Do not use any type assertions.", "7"], + [0, 0, 0, "Unexpected any. Specify a different type.", "8"], + [0, 0, 0, "Unexpected any. Specify a different type.", "9"], + [0, 0, 0, "Unexpected any. Specify a different type.", "10"], + [0, 0, 0, "Unexpected any. Specify a different type.", "11"] ], "packages/grafana-data/src/utils/OptionsUIBuilders.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], @@ -648,10 +660,13 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "2"] ], "packages/grafana-data/src/vector/ArrayVector.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] + [0, 0, 0, "Unexpected any. Specify a different type.", "0"], + [0, 0, 0, "Unexpected any. Specify a different type.", "1"], + [0, 0, 0, "Unexpected any. Specify a different type.", "2"] ], "packages/grafana-data/src/vector/CircularVector.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] + [0, 0, 0, "Unexpected any. Specify a different type.", "0"], + [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], "packages/grafana-data/src/vector/ConstantVector.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] @@ -661,7 +676,16 @@ exports[`better eslint`] = { ], "packages/grafana-data/src/vector/FunctionalVector.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"] + [0, 0, 0, "Unexpected any. Specify a different type.", "1"], + [0, 0, 0, "Unexpected any. Specify a different type.", "2"], + [0, 0, 0, "Unexpected any. Specify a different type.", "3"], + [0, 0, 0, "Unexpected any. Specify a different type.", "4"], + [0, 0, 0, "Unexpected any. Specify a different type.", "5"], + [0, 0, 0, "Unexpected any. Specify a different type.", "6"], + [0, 0, 0, "Unexpected any. Specify a different type.", "7"], + [0, 0, 0, "Unexpected any. Specify a different type.", "8"], + [0, 0, 0, "Unexpected any. Specify a different type.", "9"], + [0, 0, 0, "Unexpected any. Specify a different type.", "10"] ], "packages/grafana-data/src/vector/SortedVector.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] diff --git a/packages/grafana-data/src/dataframe/ArrayDataFrame.ts b/packages/grafana-data/src/dataframe/ArrayDataFrame.ts index 2b6132526ad..36ea2d43fc1 100644 --- a/packages/grafana-data/src/dataframe/ArrayDataFrame.ts +++ b/packages/grafana-data/src/dataframe/ArrayDataFrame.ts @@ -1,4 +1,4 @@ -import { Vector, QueryResultMeta } from '../types'; +import { QueryResultMeta } from '../types'; import { Field, FieldType, DataFrame } from '../types/dataFrame'; import { FunctionalVector } from '../vector/FunctionalVector'; import { vectorToArray } from '../vector/vectorToArray'; @@ -10,10 +10,12 @@ export type ValueConverter = (val: unknown) => T; const NOOP: ValueConverter = (v) => v; -class ArrayPropertyVector implements Vector { +class ArrayPropertyVector extends FunctionalVector { converter = NOOP; - constructor(private source: any[], private prop: string) {} + constructor(private source: any[], private prop: string) { + super(); + } get length(): number { return this.source.length; diff --git a/packages/grafana-data/src/dataframe/DataFrameJSON.test.ts b/packages/grafana-data/src/dataframe/DataFrameJSON.test.ts index fe6930eaf2f..a767faf0895 100644 --- a/packages/grafana-data/src/dataframe/DataFrameJSON.test.ts +++ b/packages/grafana-data/src/dataframe/DataFrameJSON.test.ts @@ -235,7 +235,7 @@ describe('DataFrame JSON', () => { }, }; - expect(dataFrameToJSON(inputFrame)).toStrictEqual(expectedJSON); + expect(dataFrameToJSON(inputFrame)).toEqual(expectedJSON); }); }); }); diff --git a/packages/grafana-data/src/dataframe/MutableDataFrame.ts b/packages/grafana-data/src/dataframe/MutableDataFrame.ts index 8d5c6964147..5deccf1dac0 100644 --- a/packages/grafana-data/src/dataframe/MutableDataFrame.ts +++ b/packages/grafana-data/src/dataframe/MutableDataFrame.ts @@ -2,20 +2,20 @@ import { isString } from 'lodash'; import { QueryResultMeta } from '../types/data'; import { Field, DataFrame, DataFrameDTO, FieldDTO, FieldType } from '../types/dataFrame'; -import { MutableVector, Vector } from '../types/vector'; +import { Vector } from '../types/vector'; import { makeFieldParser } from '../utils/fieldParser'; import { ArrayVector } from '../vector/ArrayVector'; import { FunctionalVector } from '../vector/FunctionalVector'; import { guessFieldTypeFromValue, guessFieldTypeForField, toDataFrameDTO } from './processDataFrame'; -export type MutableField = Field>; +export type MutableField = Field; -type MutableVectorCreator = (buffer?: any[]) => MutableVector; +type MutableVectorCreator = (buffer?: any[]) => Vector; export const MISSING_VALUE = undefined; // Treated as connected in new graph panel -export class MutableDataFrame extends FunctionalVector implements DataFrame, MutableVector { +export class MutableDataFrame extends FunctionalVector implements DataFrame { name?: string; refId?: string; meta?: QueryResultMeta; @@ -148,15 +148,6 @@ export class MutableDataFrame extends FunctionalVector implements Da } } - /** - * Reverse all values - */ - reverse() { - for (const f of this.fields) { - f.values.reverse(); - } - } - private parsers: Map any> | undefined = undefined; /** @@ -210,10 +201,25 @@ export class MutableDataFrame extends FunctionalVector implements Da } } + /** support standard array push syntax */ + push(...vals: T[]): number { + for (const v of vals) { + this.add(v); + } + return this.length; + } + + reverse() { + for (const field of this.fields) { + field.values.reverse(); + } + return this; + } + /** * Add values from an object to corresponding fields. Similar to appendRow but does not create new fields. */ - add(value: T) { + add(value: T): void { // Will add one value for every field const obj = value as any; for (const field of this.fields) { diff --git a/packages/grafana-data/src/transformations/transformers/joinDataFrames.test.ts b/packages/grafana-data/src/transformations/transformers/joinDataFrames.test.ts index 5f4ef23427b..9c3b030ea28 100644 --- a/packages/grafana-data/src/transformations/transformers/joinDataFrames.test.ts +++ b/packages/grafana-data/src/transformations/transformers/joinDataFrames.test.ts @@ -332,7 +332,7 @@ describe('align frames', () => { const v = new ArrayVector([null, null, null, 4, null]); expect(isLikelyAscendingVector(v)).toBeTruthy(); expect(isLikelyAscendingVector(new ArrayVector([4]))).toBeTruthy(); - expect(isLikelyAscendingVector(new ArrayVector([]))).toBeTruthy(); + expect(isLikelyAscendingVector(new ArrayVector([] as any[]))).toBeTruthy(); }); it('middle values', () => { diff --git a/packages/grafana-data/src/transformations/transformers/reduce.test.ts b/packages/grafana-data/src/transformations/transformers/reduce.test.ts index 6ad69449486..16a6dfb1173 100644 --- a/packages/grafana-data/src/transformations/transformers/reduce.test.ts +++ b/packages/grafana-data/src/transformations/transformers/reduce.test.ts @@ -418,7 +418,7 @@ describe('Reducer Transformer', () => { "CA", "NY", "CA", - , + undefined, ], }, { @@ -426,8 +426,8 @@ describe('Reducer Transformer', () => { "name": "country", "type": "string", "values": [ - , - , + undefined, + undefined, "USA", "USA", ], diff --git a/packages/grafana-data/src/types/dataFrame.ts b/packages/grafana-data/src/types/dataFrame.ts index d7200416ce4..639a3770b76 100644 --- a/packages/grafana-data/src/types/dataFrame.ts +++ b/packages/grafana-data/src/types/dataFrame.ts @@ -136,7 +136,13 @@ export interface Field> { * Meta info about how field and how to display it */ config: FieldConfig; - values: V; // The raw field values + + /** + * The raw field values + * In Grafana 10, this accepts both simple arrays and the Vector interface + * In Grafana 11, the Vector interface will be removed + */ + values: V | T[]; /** * When type === FieldType.Time, this can optionally store diff --git a/packages/grafana-data/src/types/vector.ts b/packages/grafana-data/src/types/vector.ts index 76f2038e71c..29e7fc04d51 100644 --- a/packages/grafana-data/src/types/vector.ts +++ b/packages/grafana-data/src/types/vector.ts @@ -1,35 +1,99 @@ -export interface Vector { +declare global { + interface Array { + /** @deprecated this only exists to help migrate Vector to Array */ + get(idx: number): T; + /** @deprecated this only exists to help migrate Vector to Array */ + set(idx: number, value: T): void; + /** @deprecated this only exists to help migrate Vector to Array */ + add(value: T): void; + /** @deprecated this only exists to help migrate Vector to Array */ + toArray(): T[]; + } +} + +// JS original sin +// this if condition is because Jest will re-exec this block multiple times (in a browser this only runs once) +if (!Object.getOwnPropertyDescriptor(Array.prototype, 'toArray')) { + Object.defineProperties(Array.prototype, { + get: { + value: function (idx: number): any { + return (this as any)[idx]; + }, + writable: false, + enumerable: false, + configurable: false, + }, + set: { + value: function (idx: number, value: any) { + (this as any)[idx] = value; + }, + writable: false, + enumerable: false, + configurable: false, + }, + add: { + value: function (value: any) { + (this as any).push(value); + }, + writable: false, + enumerable: false, + configurable: false, + }, + toArray: { + value: function () { + return this; + }, + writable: false, + enumerable: false, + configurable: false, + }, + }); +} + +/** @deprecated use a simple Array */ +export interface Vector extends Array { length: number; /** * Access the value by index (Like an array) + * + * @deprecated use a simple Array */ get(index: number): T; + /** + * Set a value + * + * @deprecated use a simple Array + */ + set: (index: number, value: T) => void; + + /** + * Adds the value to the vector + * Same as Array.push() + * + * @deprecated use a simple Array + */ + add: (value: T) => void; + /** * Get the results as an array. + * + * @deprecated use a simple Array */ toArray(): T[]; } /** * Apache arrow vectors are Read/Write + * + * @deprecated -- this is now part of the base Vector interface */ -export interface ReadWriteVector extends Vector { - set: (index: number, value: T) => void; -} +export interface ReadWriteVector extends Vector {} /** * Vector with standard manipulation functions + * + * @deprecated -- this is now part of the base Vector interface */ -export interface MutableVector extends ReadWriteVector { - /** - * Adds the value to the vector - */ - add: (value: T) => void; - - /** - * modifies the vector so it is now the opposite order - */ - reverse: () => void; -} +export interface MutableVector extends ReadWriteVector {} diff --git a/packages/grafana-data/src/vector/AppendedVectors.ts b/packages/grafana-data/src/vector/AppendedVectors.ts index cc33811f731..66883c102cd 100644 --- a/packages/grafana-data/src/vector/AppendedVectors.ts +++ b/packages/grafana-data/src/vector/AppendedVectors.ts @@ -1,5 +1,6 @@ import { Vector } from '../types/vector'; +import { FunctionalVector } from './FunctionalVector'; import { vectorToArray } from './vectorToArray'; interface AppendedVectorInfo { @@ -13,11 +14,12 @@ interface AppendedVectorInfo { * 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 implements Vector { +export class AppendedVectors extends FunctionalVector { length = 0; source: Array> = []; constructor(startAt = 0) { + super(); this.length = startAt; } diff --git a/packages/grafana-data/src/vector/ArrayVector.test.ts b/packages/grafana-data/src/vector/ArrayVector.test.ts new file mode 100644 index 00000000000..119e42579df --- /dev/null +++ b/packages/grafana-data/src/vector/ArrayVector.test.ts @@ -0,0 +1,41 @@ +import { Field, FieldType } from '../types'; + +import { ArrayVector } from './ArrayVector'; + +describe('ArrayVector', () => { + it('should init 150k with 65k Array.push() chonking', () => { + const arr = Array.from({ length: 150e3 }, (v, i) => i); + const av = new ArrayVector(arr); + + expect(av.toArray()).toEqual(arr); + }); + + it('should support add and push', () => { + const av = new ArrayVector(); + av.add(1); + av.push(2); + av.push(3, 4); + + expect(av.toArray()).toEqual([1, 2, 3, 4]); + }); + + it('typescript should not re-define the ArrayVector based on input to the constructor', () => { + const field: Field = { + name: 'test', + config: {}, + type: FieldType.number, + values: new ArrayVector(), // this defaults to `new ArrayVector()` + }; + expect(field).toBeDefined(); + + // Before collapsing Vector, ReadWriteVector, and MutableVector these all worked fine + field.values = new ArrayVector(); + field.values = new ArrayVector(undefined); + field.values = new ArrayVector([1, 2, 3]); + field.values = new ArrayVector([]); + field.values = new ArrayVector([1, undefined]); + field.values = new ArrayVector([null]); + field.values = new ArrayVector(['a', 'b', 'c']); + expect(field.values.length).toBe(3); + }); +}); diff --git a/packages/grafana-data/src/vector/ArrayVector.ts b/packages/grafana-data/src/vector/ArrayVector.ts index d153cfb636c..121276503f5 100644 --- a/packages/grafana-data/src/vector/ArrayVector.ts +++ b/packages/grafana-data/src/vector/ArrayVector.ts @@ -1,43 +1,38 @@ -import { MutableVector } from '../types/vector'; - -import { FunctionalVector } from './FunctionalVector'; - /** * @public + * + * @deprecated use a simple Array */ -export class ArrayVector extends FunctionalVector implements MutableVector { - buffer: T[]; +export class ArrayVector extends Array { + get buffer() { + return this; + } - constructor(buffer?: T[]) { + set buffer(values: any[]) { + this.length = 0; + + const len = values?.length; + + if (len) { + let chonkSize = 65e3; + let numChonks = Math.ceil(len / chonkSize); + + for (let chonkIdx = 0; chonkIdx < numChonks; chonkIdx++) { + this.push.apply(this, values.slice(chonkIdx * chonkSize, (chonkIdx + 1) * chonkSize)); + } + } + } + + /** + * This any type is here to make the change type changes in v10 non breaking for plugins. + * Before you could technically assign field.values any typed ArrayVector no matter what the Field T type was. + */ + constructor(buffer?: any[]) { super(); - 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; + this.buffer = buffer ?? []; } toJSON(): T[] { - return this.buffer; + return [...this]; // copy to avoid circular reference (only for jest) } } diff --git a/packages/grafana-data/src/vector/BinaryOperationVector.ts b/packages/grafana-data/src/vector/BinaryOperationVector.ts index a8ea3024bec..51ecb808ec4 100644 --- a/packages/grafana-data/src/vector/BinaryOperationVector.ts +++ b/packages/grafana-data/src/vector/BinaryOperationVector.ts @@ -1,13 +1,16 @@ import { Vector } from '../types/vector'; import { BinaryOperation } from '../utils/binaryOperators'; +import { FunctionalVector } from './FunctionalVector'; import { vectorToArray } from './vectorToArray'; /** * @public */ -export class BinaryOperationVector implements Vector { - constructor(private left: Vector, private right: Vector, private operation: BinaryOperation) {} +export class BinaryOperationVector extends FunctionalVector { + constructor(private left: Vector, private right: Vector, private operation: BinaryOperation) { + super(); + } get length(): number { return this.left.length; diff --git a/packages/grafana-data/src/vector/CircularVector.test.ts b/packages/grafana-data/src/vector/CircularVector.test.ts index abffd15846b..3ed934e89bc 100644 --- a/packages/grafana-data/src/vector/CircularVector.test.ts +++ b/packages/grafana-data/src/vector/CircularVector.test.ts @@ -20,6 +20,9 @@ describe('Check Circular Vector', () => { v.add(8); expect(v.toArray()).toEqual([6, 7, 8]); + + v.push(9, 10); + expect(v.toArray()).toEqual([8, 9, 10]); }); it('should grow buffer until it hits capacity (append)', () => { diff --git a/packages/grafana-data/src/vector/CircularVector.ts b/packages/grafana-data/src/vector/CircularVector.ts index 6d848ad40f4..42ea9604b12 100644 --- a/packages/grafana-data/src/vector/CircularVector.ts +++ b/packages/grafana-data/src/vector/CircularVector.ts @@ -1,7 +1,4 @@ -import { MutableVector } from '../types/vector'; - import { FunctionalVector } from './FunctionalVector'; -import { vectorToArray } from './vectorToArray'; interface CircularOptions { buffer?: T[]; @@ -18,7 +15,7 @@ interface CircularOptions { * * @public */ -export class CircularVector extends FunctionalVector implements MutableVector { +export class CircularVector extends FunctionalVector { private buffer: T[]; private index: number; private capacity: number; @@ -43,7 +40,7 @@ export class CircularVector extends FunctionalVector implements Muta * * head vs tail * * growing buffer vs overwriting values */ - private getAddFunction() { + private getAddFunction(): (value: T) => void { // When we are not at capacity, it should actually modify the buffer if (this.capacity > this.buffer.length) { if (this.tail) { @@ -114,31 +111,18 @@ export class CircularVector extends FunctionalVector implements Muta } reverse() { - this.buffer.reverse(); + return 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) { + set(index: number, value: any) { 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); - } } diff --git a/packages/grafana-data/src/vector/ConstantVector.ts b/packages/grafana-data/src/vector/ConstantVector.ts index 2024514f506..ff39aaacfdb 100644 --- a/packages/grafana-data/src/vector/ConstantVector.ts +++ b/packages/grafana-data/src/vector/ConstantVector.ts @@ -1,10 +1,12 @@ -import { Vector } from '../types/vector'; +import { FunctionalVector } from './FunctionalVector'; /** * @public */ -export class ConstantVector implements Vector { - constructor(private value: T, private len: number) {} +export class ConstantVector extends FunctionalVector { + constructor(private value: T, private len: number) { + super(); + } get length() { return this.len; diff --git a/packages/grafana-data/src/vector/FunctionalVector.ts b/packages/grafana-data/src/vector/FunctionalVector.ts index b9f4e22907b..9e5ba6b77e7 100644 --- a/packages/grafana-data/src/vector/FunctionalVector.ts +++ b/packages/grafana-data/src/vector/FunctionalVector.ts @@ -3,7 +3,7 @@ import { Vector } from '../types'; import { vectorToArray } from './vectorToArray'; /** @public */ -export abstract class FunctionalVector implements Vector, Iterable { +export abstract class FunctionalVector implements Vector { abstract get length(): number; abstract get(index: number): T; @@ -15,32 +15,176 @@ export abstract class FunctionalVector implements Vector, Iterable void) { + forEach(iterator: (row: T, index: number, array: T[]) => void): void { return vectorator(this).forEach(iterator); } - map(transform: (item: T, index: number) => V) { + map(transform: (item: T, index: number, array: T[]) => V): V[] { return vectorator(this).map(transform); } - filter(predicate: (item: T) => boolean): T[] { + filter(predicate: (item: T, index: number, array: T[]) => boolean): T[] { return vectorator(this).filter(predicate); } + at(index: number): T | undefined { + return this.get(index); + } + toArray(): T[] { return vectorToArray(this); } + join(separator?: string | undefined): string { + return this.toArray().join(separator); + } + toJSON(): any { return this.toArray(); } + + //-------------------------- + // Method not implemented + //-------------------------- + + [n: number]: T; + + pop(): T | undefined { + throw new Error('Method not implemented.'); + } + concat(...items: Array>): T[]; + concat(...items: Array>): T[] { + throw new Error('Method not implemented.'); + } + reverse(): T[] { + throw new Error('Method not implemented.'); + } + shift(): T | undefined { + throw new Error('Method not implemented.'); + } + slice(start?: number | undefined, end?: number | undefined): T[] { + throw new Error('Method not implemented.'); + } + sort(compareFn?: ((a: T, b: T) => number) | undefined): this { + throw new Error('Method not implemented.'); + } + splice(start: number, deleteCount?: number | undefined): T[]; + splice(start: number, deleteCount: number, ...items: T[]): T[] { + throw new Error('Method not implemented.'); + } + unshift(...items: T[]): number { + throw new Error('Method not implemented.'); + } + fill(value: T, start?: number | undefined, end?: number | undefined): this { + throw new Error('Method not implemented.'); + } + copyWithin(target: number, start: number, end?: number | undefined): this { + throw new Error('Method not implemented.'); + } + + [Symbol.unscopables](): { + copyWithin: boolean; + entries: boolean; + fill: boolean; + find: boolean; + findIndex: boolean; + keys: boolean; + values: boolean; + } { + throw new Error('Method not implemented.'); + } + + //-------------------------------------------------------------------------------- + // Delegated Array function -- these will not be efficient :grimmice: + //-------------------------------------------------------------------------------- + + indexOf(searchElement: T, fromIndex?: number | undefined): number { + return this.toArray().indexOf(searchElement, fromIndex); + } + lastIndexOf(searchElement: T, fromIndex?: number | undefined): number { + return this.toArray().lastIndexOf(searchElement, fromIndex); + } + every(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): this is S[]; + every(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean; + every(predicate: any, thisArg?: unknown): boolean { + return this.toArray().every(predicate, thisArg); + } + some(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean { + return this.toArray().some(predicate, thisArg); + } + reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T; + reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T; + reduce(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U; + reduce(callbackfn: unknown, initialValue?: unknown): T { + throw new Error('Method not implemented.'); + } + reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T; + reduceRight( + callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, + initialValue: T + ): T; + reduceRight( + callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, + initialValue: U + ): U; + reduceRight(callbackfn: unknown, initialValue?: unknown): T { + throw new Error('Method not implemented.'); + } + find( + predicate: (this: void, value: T, index: number, obj: T[]) => value is S, + thisArg?: any + ): S | undefined; + find(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): T | undefined { + return this.toArray().find(predicate, thisArg); + } + findIndex(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): number { + return this.toArray().findIndex(predicate, thisArg); + } + entries(): IterableIterator<[number, T]> { + return this.toArray().entries(); + } + keys(): IterableIterator { + return this.toArray().keys(); + } + values(): IterableIterator { + return this.toArray().values(); + } + includes(searchElement: T, fromIndex?: number | undefined): boolean { + return this.toArray().includes(searchElement, fromIndex); + } + flatMap( + callback: (this: This, value: T, index: number, array: T[]) => U | readonly U[], + thisArg?: This | undefined + ): U[] { + return this.toArray().flatMap(callback, thisArg); + } + flat(this: A, depth?: D | undefined): Array> { + throw new Error('Method not implemented.'); + } } +const emptyarray: any[] = []; + /** * Use functional programming with your vector */ @@ -52,25 +196,26 @@ export function vectorator(vector: Vector) { } }, - forEach(iterator: (row: T) => void) { + forEach(iterator: (row: T, index: number, array: T[]) => void): void { for (let i = 0; i < vector.length; i++) { - iterator(vector.get(i)); + iterator(vector.get(i), i, emptyarray); } }, - map(transform: (item: T, index: number) => V) { + map(transform: (item: T, index: number, array: T[]) => V): V[] { const result: V[] = []; for (let i = 0; i < vector.length; i++) { - result.push(transform(vector.get(i), i)); + result.push(transform(vector.get(i), i, emptyarray)); } return result; }, /** Add a predicate where you return true if it should *keep* the value */ - filter(predicate: (item: T) => boolean): T[] { + filter(predicate: (item: T, index: number, array: T[]) => boolean): T[] { const result: T[] = []; + let count = 0; for (const val of this) { - if (predicate(val)) { + if (predicate(val, count++, emptyarray)) { result.push(val); } } diff --git a/packages/grafana-data/src/vector/IndexVector.ts b/packages/grafana-data/src/vector/IndexVector.ts index 4a7fc3dfc27..f69fb51dc5d 100644 --- a/packages/grafana-data/src/vector/IndexVector.ts +++ b/packages/grafana-data/src/vector/IndexVector.ts @@ -6,6 +6,8 @@ import { FunctionalVector } from './FunctionalVector'; * IndexVector is a simple vector implementation that returns the index value * for each element in the vector. It is functionally equivolant a vector backed * by an array with values: `[0,1,2,...,length-1]` + * + * @deprecated use a simple Arrays */ export class IndexVector extends FunctionalVector { constructor(private len: number) { diff --git a/packages/grafana-data/src/vector/RowVector.ts b/packages/grafana-data/src/vector/RowVector.ts index 21746be48fd..abd2077c564 100644 --- a/packages/grafana-data/src/vector/RowVector.ts +++ b/packages/grafana-data/src/vector/RowVector.ts @@ -1,13 +1,16 @@ import { Vector } from '../types'; +import { FunctionalVector } from './FunctionalVector'; import { vectorToArray } from './vectorToArray'; /** * RowVector makes the row values look like a vector * @internal */ -export class RowVector implements Vector { - constructor(private columns: Vector[]) {} +export class RowVector extends FunctionalVector { + constructor(private columns: Vector[]) { + super(); + } rowIndex = 0; diff --git a/packages/grafana-data/src/vector/SortedVector.ts b/packages/grafana-data/src/vector/SortedVector.ts index e2a318e95d4..780005c0000 100644 --- a/packages/grafana-data/src/vector/SortedVector.ts +++ b/packages/grafana-data/src/vector/SortedVector.ts @@ -1,12 +1,17 @@ import { Vector } from '../types/vector'; +import { FunctionalVector } from './FunctionalVector'; import { vectorToArray } from './vectorToArray'; /** * Values are returned in the order defined by the input parameter + * + * @deprecated use a simple Arrays */ -export class SortedVector implements Vector { - constructor(private source: Vector, private order: number[]) {} +export class SortedVector extends FunctionalVector { + constructor(private source: Vector, private order: number[]) { + super(); + } get length(): number { return this.source.length; diff --git a/packages/grafana-data/src/vector/vectorToArray.ts b/packages/grafana-data/src/vector/vectorToArray.ts index a0a17d71cb9..7ec23d9c261 100644 --- a/packages/grafana-data/src/vector/vectorToArray.ts +++ b/packages/grafana-data/src/vector/vectorToArray.ts @@ -1,5 +1,6 @@ import { Vector } from '../types/vector'; +/** @deprecated use a simple Arrays */ export function vectorToArray(v: Vector): T[] { const arr: T[] = Array(v.length); for (let i = 0; i < v.length; i++) { diff --git a/packages/grafana-ui/src/components/GraphNG/nullInsertThreshold.test.ts b/packages/grafana-ui/src/components/GraphNG/nullInsertThreshold.test.ts index ea2fe6f50f6..6ffa6408d0e 100644 --- a/packages/grafana-ui/src/components/GraphNG/nullInsertThreshold.test.ts +++ b/packages/grafana-ui/src/components/GraphNG/nullInsertThreshold.test.ts @@ -59,9 +59,9 @@ describe('nullInsertThreshold Transformer', () => { const result = applyNullInsertThreshold({ frame: df }); - expect(result.fields[0].values.toArray()).toStrictEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - expect(result.fields[1].values.toArray()).toStrictEqual([4, null, 6, null, null, null, null, null, null, 8]); - expect(result.fields[2].values.toArray()).toStrictEqual(['a', null, 'b', null, null, null, null, null, null, 'c']); + expect(result.fields[0].values).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + expect(result.fields[1].values).toEqual([4, null, 6, null, null, null, null, null, null, 8]); + expect(result.fields[2].values).toEqual(['a', null, 'b', null, null, null, null, null, null, 'c']); }); test('should insert nulls at +threshold between adjacent > threshold: 2', () => { @@ -76,9 +76,9 @@ describe('nullInsertThreshold Transformer', () => { const result = applyNullInsertThreshold({ frame: df }); - expect(result.fields[0].values.toArray()).toStrictEqual([5, 7, 9, 11]); - expect(result.fields[1].values.toArray()).toStrictEqual([4, 6, null, 8]); - expect(result.fields[2].values.toArray()).toStrictEqual(['a', 'b', null, 'c']); + expect(result.fields[0].values).toEqual([5, 7, 9, 11]); + expect(result.fields[1].values).toEqual([4, 6, null, 8]); + expect(result.fields[2].values).toEqual(['a', 'b', null, 'c']); }); test('should insert nulls at +interval between adjacent > interval: 1', () => { @@ -93,9 +93,9 @@ describe('nullInsertThreshold Transformer', () => { const result = applyNullInsertThreshold({ frame: df }); - expect(result.fields[0].values.toArray()).toStrictEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - expect(result.fields[1].values.toArray()).toStrictEqual([4, null, 6, null, null, null, null, null, null, 8]); - expect(result.fields[2].values.toArray()).toStrictEqual(['a', null, 'b', null, null, null, null, null, null, 'c']); + expect(result.fields[0].values).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + expect(result.fields[1].values).toEqual([4, null, 6, null, null, null, null, null, null, 8]); + expect(result.fields[2].values).toEqual(['a', null, 'b', null, null, null, null, null, null, 'c']); }); test('should insert leading null at beginning +interval when timeRange.from.valueOf() exceeds threshold', () => { @@ -115,8 +115,8 @@ describe('nullInsertThreshold Transformer', () => { refFieldPseudoMax: 13, }); - expect(result.fields[0].values.toArray()).toStrictEqual([-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]); - expect(result.fields[1].values.toArray()).toStrictEqual([ + expect(result.fields[0].values).toEqual([-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]); + expect(result.fields[1].values).toEqual([ null, null, null, @@ -133,7 +133,7 @@ describe('nullInsertThreshold Transformer', () => { null, 8, ]); - expect(result.fields[2].values.toArray()).toStrictEqual([ + expect(result.fields[2].values).toEqual([ null, null, null, @@ -164,35 +164,9 @@ describe('nullInsertThreshold Transformer', () => { const result = applyNullInsertThreshold({ frame: df, refFieldName: null, refFieldPseudoMax: 13 }); - expect(result.fields[0].values.toArray()).toStrictEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); - expect(result.fields[1].values.toArray()).toStrictEqual([ - 4, - null, - 6, - null, - null, - null, - null, - null, - null, - 8, - null, - null, - ]); - expect(result.fields[2].values.toArray()).toStrictEqual([ - 'a', - null, - 'b', - null, - null, - null, - null, - null, - null, - 'c', - null, - null, - ]); + expect(result.fields[0].values).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); + expect(result.fields[1].values).toEqual([4, null, 6, null, null, null, null, null, null, 8, null, null]); + expect(result.fields[2].values).toEqual(['a', null, 'b', null, null, null, null, null, null, 'c', null, null]); // should work for frames with 1 datapoint const df2 = new MutableDataFrame({ @@ -208,9 +182,9 @@ describe('nullInsertThreshold Transformer', () => { // we get 12 nulls instead of the additional 1 const result2 = applyNullInsertThreshold({ frame: df2, refFieldName: null, refFieldPseudoMax: 2.5 }); - expect(result2.fields[0].values.toArray()).toStrictEqual([1, 2]); - expect(result2.fields[1].values.toArray()).toStrictEqual([1, null]); - expect(result2.fields[2].values.toArray()).toStrictEqual(['a', null]); + expect(result2.fields[0].values).toEqual([1, 2]); + expect(result2.fields[1].values).toEqual([1, null]); + expect(result2.fields[2].values).toEqual(['a', null]); }); test('should not insert trailing null at end +interval when timeRange.to.valueOf() equals threshold', () => { @@ -225,9 +199,9 @@ describe('nullInsertThreshold Transformer', () => { const result = applyNullInsertThreshold({ frame: df, refFieldName: null, refFieldPseudoMax: 2 }); - expect(result.fields[0].values.toArray()).toStrictEqual([1]); - expect(result.fields[1].values.toArray()).toStrictEqual([1]); - expect(result.fields[2].values.toArray()).toStrictEqual(['a']); + expect(result.fields[0].values).toEqual([1]); + expect(result.fields[1].values).toEqual([1]); + expect(result.fields[2].values).toEqual(['a']); }); // TODO: make this work @@ -243,9 +217,9 @@ describe('nullInsertThreshold Transformer', () => { const result = applyNullInsertThreshold({ frame: df }); - expect(result.fields[0].values.toArray()).toStrictEqual([5, 6, 7, 8, 11]); - expect(result.fields[1].values.toArray()).toStrictEqual([4, null, 6, null, 8]); - expect(result.fields[2].values.toArray()).toStrictEqual(['a', null, 'b', null, 'c']); + expect(result.fields[0].values).toEqual([5, 6, 7, 8, 11]); + expect(result.fields[1].values).toEqual([4, null, 6, null, 8]); + expect(result.fields[2].values).toEqual(['a', null, 'b', null, 'c']); }); test('should noop on 0 datapoints', () => { diff --git a/packages/grafana-ui/src/components/GraphNG/nullToValue.test.ts b/packages/grafana-ui/src/components/GraphNG/nullToValue.test.ts index f5c3bfb1a7e..a321f6a7e2f 100644 --- a/packages/grafana-ui/src/components/GraphNG/nullToValue.test.ts +++ b/packages/grafana-ui/src/components/GraphNG/nullToValue.test.ts @@ -26,9 +26,9 @@ describe('nullToValue Transformer', () => { const result = nullToValue(applyNullInsertThreshold({ frame: df })); - expect(result.fields[0].values.toArray()).toStrictEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - expect(result.fields[1].values.toArray()).toStrictEqual([4, 0, 6, 0, 0, 0, 0, 0, 0, 8]); - expect(result.fields[2].values.toArray()).toStrictEqual(['a', 0, 'b', 0, 0, 0, 0, 0, 0, 'c']); + expect(result.fields[0].values).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + expect(result.fields[1].values).toEqual([4, 0, 6, 0, 0, 0, 0, 0, 0, 8]); + expect(result.fields[2].values).toEqual(['a', 0, 'b', 0, 0, 0, 0, 0, 0, 'c']); }); test('should change all nulls to configured positive value', () => { @@ -53,9 +53,9 @@ describe('nullToValue Transformer', () => { const result = nullToValue(applyNullInsertThreshold({ frame: df })); - expect(result.fields[0].values.toArray()).toStrictEqual([5, 7, 9, 11]); - expect(result.fields[1].values.toArray()).toStrictEqual([4, 6, 1, 8]); - expect(result.fields[2].values.toArray()).toStrictEqual(['a', 'b', 1, 'c']); + expect(result.fields[0].values).toEqual([5, 7, 9, 11]); + expect(result.fields[1].values).toEqual([4, 6, 1, 8]); + expect(result.fields[2].values).toEqual(['a', 'b', 1, 'c']); }); test('should change all nulls to configured negative value', () => { @@ -70,9 +70,9 @@ describe('nullToValue Transformer', () => { const result = nullToValue(applyNullInsertThreshold({ frame: df })); - expect(result.fields[0].values.toArray()).toStrictEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - expect(result.fields[1].values.toArray()).toStrictEqual([4, -1, 6, -1, -1, -1, -1, -1, -1, 8]); - expect(result.fields[2].values.toArray()).toStrictEqual(['a', -1, 'b', -1, -1, -1, -1, -1, -1, 'c']); + expect(result.fields[0].values).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + expect(result.fields[1].values).toEqual([4, -1, 6, -1, -1, -1, -1, -1, -1, 8]); + expect(result.fields[2].values).toEqual(['a', -1, 'b', -1, -1, -1, -1, -1, -1, 'c']); }); test('should have no effect without nulls', () => { @@ -87,8 +87,8 @@ describe('nullToValue Transformer', () => { const result = nullToValue(applyNullInsertThreshold({ frame: df, refFieldName: null })); - expect(result.fields[0].values.toArray()).toStrictEqual([1, 2, 3]); - expect(result.fields[1].values.toArray()).toStrictEqual([4, 6, 8]); - expect(result.fields[2].values.toArray()).toStrictEqual(['a', 'b', 'c']); + expect(result.fields[0].values).toEqual([1, 2, 3]); + expect(result.fields[1].values).toEqual([4, 6, 8]); + expect(result.fields[2].values).toEqual(['a', 'b', 'c']); }); }); diff --git a/public/app/features/geo/format/utils.ts b/public/app/features/geo/format/utils.ts index c5d5814894d..e4b12b829e2 100644 --- a/public/app/features/geo/format/utils.ts +++ b/public/app/features/geo/format/utils.ts @@ -8,7 +8,7 @@ import { Gazetteer } from '../gazetteer/gazetteer'; import { decodeGeohash } from './geohash'; -export function pointFieldFromGeohash(geohash: Field): Field { +export function pointFieldFromGeohash(geohash: Field): Field { return { name: geohash.name ?? 'Point', type: FieldType.geo, @@ -25,7 +25,7 @@ export function pointFieldFromGeohash(geohash: Field): Field { }; } -export function pointFieldFromLonLat(lon: Field, lat: Field): Field { +export function pointFieldFromLonLat(lon: Field, lat: Field): Field { const buffer = new Array(lon.values.length); for (let i = 0; i < lon.values.length; i++) { const longitude = lon.values.get(i); diff --git a/public/app/features/geo/gazetteer/gazetteer.ts b/public/app/features/geo/gazetteer/gazetteer.ts index 94b2b81a96a..80bb0864839 100644 --- a/public/app/features/geo/gazetteer/gazetteer.ts +++ b/public/app/features/geo/gazetteer/gazetteer.ts @@ -60,7 +60,7 @@ export function loadGazetteer(path: string, data: any): Gazetteer { export function frameAsGazetter(frame: DataFrame, opts: { path: string; keys?: string[] }): Gazetteer { const keys: Field[] = []; - let geo: Field | undefined = undefined; + let geo: Field | undefined = undefined; let lat: Field | undefined = undefined; let lng: Field | undefined = undefined; let geohash: Field | undefined = undefined; @@ -132,7 +132,7 @@ export function frameAsGazetter(frame: DataFrame, opts: { path: string; keys?: s isPoint = true; } } else { - isPoint = geo.values.get(0).getType() === 'Point'; + isPoint = geo.values.get(0)?.getType() === 'Point'; } const lookup = new Map(); diff --git a/public/app/features/geo/utils/frameVectorSource.ts b/public/app/features/geo/utils/frameVectorSource.ts index 2525833fa48..17be6b05ec2 100644 --- a/public/app/features/geo/utils/frameVectorSource.ts +++ b/public/app/features/geo/utils/frameVectorSource.ts @@ -44,7 +44,7 @@ export class FrameVectorSource extends VectorSour } //eslint-disable-next-line - const field = info.field as Field; + const field = info.field as unknown as Field; const geometry = new LineString(field.values.toArray().map((p) => p.getCoordinates())) as Geometry; this.addFeatureInternal( new Feature({ diff --git a/public/app/features/geo/utils/location.ts b/public/app/features/geo/utils/location.ts index 5c0828a834f..02a25c40fa3 100644 --- a/public/app/features/geo/utils/location.ts +++ b/public/app/features/geo/utils/location.ts @@ -113,7 +113,7 @@ export interface LocationFields { h3?: Field; wkt?: Field; lookup?: Field; - geo?: Field; + geo?: Field; } export function getLocationFields(frame: DataFrame, location: LocationFieldMatchers): LocationFields { diff --git a/public/app/features/live/centrifuge/LiveDataStream.test.ts b/public/app/features/live/centrifuge/LiveDataStream.test.ts index 17b73f44e72..acae046c385 100644 --- a/public/app/features/live/centrifuge/LiveDataStream.test.ts +++ b/public/app/features/live/centrifuge/LiveDataStream.test.ts @@ -300,17 +300,13 @@ describe('LiveDataStream', () => { config: {}, name: 'time', type: 'time', - values: { - buffer: [100, 101], - }, + values: [100, 101], }, { config: {}, name: 'b', type: 'number', - values: { - buffer: [1, 2], - }, + values: [1, 2], }, ]); expect(deserializedFrame.length).toEqual(dataFrameJsons.schema1().data.values[0].length); @@ -529,17 +525,13 @@ describe('LiveDataStream', () => { config: {}, name: 'time', type: 'time', - values: { - buffer: [100, 101], - }, + values: [100, 101], }, { config: {}, name: 'b', type: 'number', - values: { - buffer: [1, 2], - }, + values: [1, 2], }, ]); expect(deserializedFrame.length).toEqual(dataFrameJsons.schema1().data.values[0].length); diff --git a/public/app/features/live/data/StreamingDataFrame.test.ts b/public/app/features/live/data/StreamingDataFrame.test.ts index fb3baab7396..97366e20d4e 100644 --- a/public/app/features/live/data/StreamingDataFrame.test.ts +++ b/public/app/features/live/data/StreamingDataFrame.test.ts @@ -90,7 +90,7 @@ describe('Streaming JSON', () => { }); it('should create frame with schema & data', () => { - expect(stream.fields.map((f) => ({ name: f.name, value: f.values.buffer }))).toMatchInlineSnapshot(` + expect(stream.fields.map((f) => ({ name: f.name, value: f.values }))).toMatchInlineSnapshot(` [ { "name": "time", @@ -127,7 +127,7 @@ describe('Streaming JSON', () => { }, }); - expect(stream.fields.map((f) => ({ name: f.name, value: f.values.buffer }))).toMatchInlineSnapshot(` + expect(stream.fields.map((f) => ({ name: f.name, value: f.values }))).toMatchInlineSnapshot(` [ { "name": "time", @@ -167,7 +167,7 @@ describe('Streaming JSON', () => { }, }); - expect(stream.fields.map((f) => ({ name: f.name, value: f.values.buffer }))).toMatchInlineSnapshot(` + expect(stream.fields.map((f) => ({ name: f.name, value: f.values }))).toMatchInlineSnapshot(` [ { "name": "time", @@ -211,7 +211,7 @@ describe('Streaming JSON', () => { }, }); - expect(stream.fields.map((f) => ({ name: f.name, value: f.values.buffer }))).toMatchInlineSnapshot(` + expect(stream.fields.map((f) => ({ name: f.name, value: f.values }))).toMatchInlineSnapshot(` [ { "name": "time", @@ -263,7 +263,7 @@ describe('Streaming JSON', () => { }, }); - expect(stream.fields.map((f) => ({ name: f.name, value: f.values.buffer }))).toMatchInlineSnapshot(` + expect(stream.fields.map((f) => ({ name: f.name, value: f.values }))).toMatchInlineSnapshot(` [ { "name": "time", @@ -558,7 +558,7 @@ describe('Streaming JSON', () => { [10, 11], ]); expect(frame.length).toEqual(3); - expect(frame.fields.map((f) => ({ name: f.name, value: f.values.buffer }))).toMatchInlineSnapshot(` + expect(frame.fields.map((f) => ({ name: f.name, value: f.values }))).toMatchInlineSnapshot(` [ { "name": "time", @@ -603,7 +603,7 @@ describe('Streaming JSON', () => { [10, 11], ]); expect(frame.length).toEqual(2); - expect(frame.fields.map((f) => ({ name: f.name, value: f.values.buffer }))).toMatchInlineSnapshot(` + expect(frame.fields.map((f) => ({ name: f.name, value: f.values }))).toMatchInlineSnapshot(` [ { "name": "time", @@ -720,7 +720,7 @@ describe('Streaming JSON', () => { }, }); - expect(stream.fields.map((f) => ({ name: f.name, labels: f.labels, values: f.values.buffer }))) + expect(stream.fields.map((f) => ({ name: f.name, labels: f.labels, values: f.values }))) .toMatchInlineSnapshot(` [ { @@ -854,7 +854,7 @@ describe('Streaming JSON', () => { const getSnapshot = (f: StreamingDataFrame) => { return { - values: f.fields[1].values.toArray(), + values: f.fields[1].values, info: f.packetInfo, }; }; diff --git a/public/app/features/live/data/StreamingDataFrame.ts b/public/app/features/live/data/StreamingDataFrame.ts index 14ffa94c85e..a4c326ccec0 100644 --- a/public/app/features/live/data/StreamingDataFrame.ts +++ b/public/app/features/live/data/StreamingDataFrame.ts @@ -1,5 +1,4 @@ import { - ArrayVector, DataFrame, DataFrameJSON, decodeFieldValueEntities, @@ -60,7 +59,7 @@ export class StreamingDataFrame implements DataFrame { refId?: string; meta: QueryResultMeta = {}; - fields: Array>> = []; + fields: Field[] = []; length = 0; private schemaFields: FieldSchema[] = []; @@ -146,11 +145,11 @@ export class StreamingDataFrame implements DataFrame { ...f, type: f.type ?? FieldType.other, config: f.config ?? {}, - values: Array.isArray(f.values) ? new ArrayVector(f.values) : new ArrayVector(), + values: f.values ?? [], })); assureValuesAreWithinLengthLimit( - this.fields.map((f) => f.values.buffer), + this.fields.map((f) => f.values), this.options.maxLength, this.timeFieldIndex, this.options.maxDelta @@ -256,8 +255,8 @@ export class StreamingDataFrame implements DataFrame { // transfer old values by type & name, unless we relied on labels to match fields values: isWide ? this.fields.find((of) => of.name === f.name && f.type === of.type)?.values ?? - new ArrayVector(Array(this.length).fill(undefined)) - : new ArrayVector(), + Array(this.length).fill(undefined) + : [], }; }); } @@ -322,7 +321,7 @@ export class StreamingDataFrame implements DataFrame { name, type, config: {}, - values: new ArrayVector([]), + values: [], }; }); } @@ -336,13 +335,14 @@ export class StreamingDataFrame implements DataFrame { this.packetInfo.action = StreamingFrameAction.Append; // mutates appended - appended = this.fields.map((f) => f.values.buffer); + appended = this.fields.map((f) => f.values); circPush(appended, values, this.options.maxLength, this.timeFieldIndex, this.options.maxDelta); } appended.forEach((v, i) => { - const { state, values } = this.fields[i]; - values.buffer = v; + const field = this.fields[i]; + const { state } = field; + field.values = v; if (state) { state.calcs = undefined; } @@ -369,7 +369,7 @@ export class StreamingDataFrame implements DataFrame { if (this.options.action === StreamingFrameAction.Append) { circPush( - this.fields.map((f) => f.values.buffer), + this.fields.map((f) => f.values), values, this.options.maxLength, this.timeFieldIndex, @@ -377,19 +377,19 @@ export class StreamingDataFrame implements DataFrame { ); } else { values.forEach((v, i) => { - if (this.fields[i]?.values) { - this.fields[i].values.buffer = v; + if (this.fields[i]) { + this.fields[i].values = v; } }); assureValuesAreWithinLengthLimit( - this.fields.map((f) => f.values.buffer), + this.fields.map((f) => f.values), this.options.maxLength, this.timeFieldIndex, this.options.maxDelta ); } - const newLength = this.fields?.[0]?.values?.buffer?.length; + const newLength = this.fields?.[0]?.values.length; if (newLength !== undefined) { this.length = newLength; } @@ -412,7 +412,7 @@ export class StreamingDataFrame implements DataFrame { getValuesFromLastPacket = (): unknown[][] => this.fields.map((f) => { - const values = f.values.buffer; + const values = f.values; return values.slice(Math.max(values.length - this.packetInfo.length)); }); @@ -449,7 +449,7 @@ export class StreamingDataFrame implements DataFrame { ...proto, config, labels: parsedLabels, - values: new ArrayVector(Array(this.length).fill(undefined)), + values: Array(this.length).fill(undefined), }); } } diff --git a/public/app/features/live/live.test.ts b/public/app/features/live/live.test.ts index 7fd6536fa69..e1627284f8a 100644 --- a/public/app/features/live/live.test.ts +++ b/public/app/features/live/live.test.ts @@ -99,25 +99,19 @@ describe('GrafanaLiveService', () => { config: {}, name: 'time', type: FieldType.time, - values: { - buffer: [100, 101], - }, + values: [100, 101], }, { config: {}, name: 'a', type: FieldType.string, - values: { - buffer: ['a', 'b'], - }, + values: ['a', 'b'], }, { config: {}, name: 'b', type: FieldType.number, - values: { - buffer: [1, 2], - }, + values: [1, 2], }, ]); }); diff --git a/public/app/features/transformers/lookupGazetteer/fieldLookup.ts b/public/app/features/transformers/lookupGazetteer/fieldLookup.ts index ccaf1da4926..4f9470c176e 100644 --- a/public/app/features/transformers/lookupGazetteer/fieldLookup.ts +++ b/public/app/features/transformers/lookupGazetteer/fieldLookup.ts @@ -1,7 +1,6 @@ import { mergeMap, from } from 'rxjs'; import { - ArrayVector, DataFrame, DataTransformerID, Field, @@ -53,12 +52,12 @@ export function addFieldsFromGazetteer(frames: DataFrame[], gaz: Gazetteer, matc //if the field matches if (matcher(field, frame, frames)) { - const values = field.values.toArray(); + const values = field.values; const sub: any[][] = []; for (const f of src) { const buffer = new Array(length); sub.push(buffer); - fields.push({ ...f, values: new ArrayVector(buffer) }); + fields.push({ ...f, values: buffer }); } // Add all values to the buffer diff --git a/public/app/features/transformers/timeSeriesTable/timeSeriesTableTransformer.test.ts b/public/app/features/transformers/timeSeriesTable/timeSeriesTableTransformer.test.ts index 952fcdf0d9b..76a8898bff2 100644 --- a/public/app/features/transformers/timeSeriesTable/timeSeriesTableTransformer.test.ts +++ b/public/app/features/transformers/timeSeriesTable/timeSeriesTableTransformer.test.ts @@ -15,8 +15,8 @@ describe('timeSeriesTableTransformer', () => { const result = results[0]; expect(result.refId).toBe('A'); expect(result.fields).toHaveLength(3); - expect(result.fields[0].values.toArray()).toEqual(['A', 'A', 'A']); - expect(result.fields[1].values.toArray()).toEqual(['B', 'C', 'D']); + expect(result.fields[0].values).toEqual(['A', 'A', 'A']); + expect(result.fields[1].values).toEqual(['B', 'C', 'D']); assertDataFrameField(result.fields[2], series); }); @@ -33,8 +33,8 @@ describe('timeSeriesTableTransformer', () => { expect(results[0]).toEqual(series[0]); expect(results[1].refId).toBe('A'); expect(results[1].fields).toHaveLength(3); - expect(results[1].fields[0].values.toArray()).toEqual(['A', 'A']); - expect(results[1].fields[1].values.toArray()).toEqual(['B', 'C']); + expect(results[1].fields[0].values).toEqual(['A', 'A']); + expect(results[1].fields[1].values).toEqual(['B', 'C']); expect(results[2]).toEqual(series[3]); }); @@ -51,14 +51,14 @@ describe('timeSeriesTableTransformer', () => { expect(results).toHaveLength(2); expect(results[0].refId).toBe('A'); expect(results[0].fields).toHaveLength(3); - expect(results[0].fields[0].values.toArray()).toEqual(['A', 'A', 'A']); - expect(results[0].fields[1].values.toArray()).toEqual(['B', 'C', 'D']); + expect(results[0].fields[0].values).toEqual(['A', 'A', 'A']); + expect(results[0].fields[1].values).toEqual(['B', 'C', 'D']); assertDataFrameField(results[0].fields[2], series.slice(0, 3)); expect(results[1].refId).toBe('B'); expect(results[1].fields).toHaveLength(4); - expect(results[1].fields[0].values.toArray()).toEqual(['B', 'B']); - expect(results[1].fields[1].values.toArray()).toEqual(['F', 'G']); - expect(results[1].fields[2].values.toArray()).toEqual(['A', 'B']); + expect(results[1].fields[0].values).toEqual(['B', 'B']); + expect(results[1].fields[1].values).toEqual(['F', 'G']); + expect(results[1].fields[2].values).toEqual(['A', 'B']); assertDataFrameField(results[1].fields[3], series.slice(3, 5)); }); }); @@ -66,12 +66,12 @@ describe('timeSeriesTableTransformer', () => { function assertFieldsEqual(field1: Field, field2: Field) { expect(field1.type).toEqual(field2.type); expect(field1.name).toEqual(field2.name); - expect(field1.values.toArray()).toEqual(field2.values.toArray()); + expect(field1.values).toEqual(field2.values); expect(field1.labels ?? {}).toEqual(field2.labels ?? {}); } function assertDataFrameField(field: Field, matchesFrames: DataFrame[]) { - const frames: DataFrame[] = field.values.toArray(); + const frames: DataFrame[] = field.values; expect(frames).toHaveLength(matchesFrames.length); frames.forEach((frame, idx) => { const matchingFrame = matchesFrames[idx]; diff --git a/public/app/features/transformers/timeSeriesTable/timeSeriesTableTransformer.ts b/public/app/features/transformers/timeSeriesTable/timeSeriesTableTransformer.ts index d3e6b68a12c..e20087a3bb3 100644 --- a/public/app/features/transformers/timeSeriesTable/timeSeriesTableTransformer.ts +++ b/public/app/features/transformers/timeSeriesTable/timeSeriesTableTransformer.ts @@ -68,7 +68,11 @@ export function timeSeriesToTableTransform(options: TimeSeriesTableTransformerOp values: new ArrayVector(), }; refId2frameField[refId] = frameField; - const table = new MutableDataFrame(); + + // NOTE: MutableDataFrame.addField() makes copies, including any .values buffers + // since we do .values.add() later on the *originals*, we pass a custom MutableVectorCreator + // which will re-use the existing empty .values buffer by reference + const table = new MutableDataFrame(undefined, (buffer) => buffer ?? []); for (const label of Object.values(labelFields)) { table.addField(label); } @@ -81,7 +85,7 @@ export function timeSeriesToTableTransform(options: TimeSeriesTableTransformerOp const labels = frame.fields[1].labels; for (const labelKey of Object.keys(labelFields)) { const labelValue = labels?.[labelKey] ?? null; - labelFields[labelKey].values.add(labelValue); + labelFields[labelKey].values.add(labelValue!); } frameField.values.add(frame); diff --git a/public/app/plugins/datasource/elasticsearch/ElasticResponse.test.ts b/public/app/plugins/datasource/elasticsearch/ElasticResponse.test.ts index 6f5955aedd9..6f19818be05 100644 --- a/public/app/plugins/datasource/elasticsearch/ElasticResponse.test.ts +++ b/public/app/plugins/datasource/elasticsearch/ElasticResponse.test.ts @@ -371,7 +371,7 @@ describe('ElasticResponse', () => { expect(getTimeField(frame1).values.get(0)).toBe(1000); expect(frame2.name).toBe('Average value'); - expect(getValueField(frame2).values.toArray()).toStrictEqual([88, 99]); + expect(getValueField(frame2).values.toArray()).toEqual([88, 99]); }); }); @@ -688,20 +688,20 @@ describe('ElasticResponse', () => { const firstSeries = result.data[0]; expect(firstSeries.name).toBe('Top Metrics @value'); expect(firstSeries.length).toBe(2); - expect(getTimeField(firstSeries).values.toArray()).toStrictEqual([ + expect(getTimeField(firstSeries).values.toArray()).toEqual([ new Date('2021-01-01T00:00:00.000Z').valueOf(), new Date('2021-01-01T00:00:10.000Z').valueOf(), ]); - expect(getValueField(firstSeries).values.toArray()).toStrictEqual([1, 1]); + expect(getValueField(firstSeries).values.toArray()).toEqual([1, 1]); const secondSeries = result.data[1]; expect(secondSeries.name).toBe('Top Metrics @anotherValue'); expect(secondSeries.length).toBe(2); - expect(getTimeField(secondSeries).values.toArray()).toStrictEqual([ + expect(getTimeField(secondSeries).values.toArray()).toEqual([ new Date('2021-01-01T00:00:00.000Z').valueOf(), new Date('2021-01-01T00:00:10.000Z').valueOf(), ]); - expect(getValueField(secondSeries).values.toArray()).toStrictEqual([2, 2]); + expect(getValueField(secondSeries).values.toArray()).toEqual([2, 2]); }); }); @@ -814,9 +814,9 @@ describe('ElasticResponse', () => { const { fields } = result.data[0]; expect(fields.length).toBe(2); expect(fields[0].name).toBe('bytes'); - expect(fields[0].config).toStrictEqual({ filterable: true }); + expect(fields[0].config).toEqual({ filterable: true }); expect(fields[1].name).toBe('Count'); - expect(fields[1].config).toStrictEqual({}); + expect(fields[1].config).toEqual({}); }); }); @@ -989,9 +989,9 @@ describe('ElasticResponse', () => { const field2 = result.data[0].fields[1]; const field3 = result.data[0].fields[2]; - expect(field1.values.toArray()).toStrictEqual(['server-1', 'server-2']); - expect(field2.values.toArray()).toStrictEqual([1000, 2000]); - expect(field3.values.toArray()).toStrictEqual([369, 200]); + expect(field1.values).toEqual(['server-1', 'server-2']); + expect(field2.values).toEqual([1000, 2000]); + expect(field3.values).toEqual([369, 200]); }); }); @@ -1044,9 +1044,9 @@ describe('ElasticResponse', () => { expect(field2.name).toBe('p75 value'); expect(field3.name).toBe('p90 value'); - expect(field1.values.toArray()).toStrictEqual(['id1', 'id2']); - expect(field2.values.toArray()).toStrictEqual([3.3, 2.3]); - expect(field3.values.toArray()).toStrictEqual([5.5, 4.5]); + expect(field1.values.toArray()).toEqual(['id1', 'id2']); + expect(field2.values.toArray()).toEqual([3.3, 2.3]); + expect(field3.values.toArray()).toEqual([5.5, 4.5]); }); }); @@ -1088,9 +1088,9 @@ describe('ElasticResponse', () => { it('should include field in metric name', () => { expect(result.data[0].length).toBe(1); expect(result.data[0].fields.length).toBe(3); - expect(result.data[0].fields[0].values.toArray()).toStrictEqual(['server-1']); - expect(result.data[0].fields[1].values.toArray()).toStrictEqual([1000]); - expect(result.data[0].fields[2].values.toArray()).toStrictEqual([3000]); + expect(result.data[0].fields[0].values.toArray()).toEqual(['server-1']); + expect(result.data[0].fields[1].values.toArray()).toEqual([1000]); + expect(result.data[0].fields[2].values.toArray()).toEqual([3000]); }); }); @@ -1286,11 +1286,11 @@ describe('ElasticResponse', () => { expect(frame.length).toBe(2); const { fields } = frame; expect(fields.length).toBe(5); - expect(fields[0].values.toArray()).toStrictEqual([1000, 2000]); - expect(fields[1].values.toArray()).toStrictEqual([2, 3]); - expect(fields[2].values.toArray()).toStrictEqual([3, 4]); - expect(fields[3].values.toArray()).toStrictEqual([6, 12]); - expect(fields[4].values.toArray()).toStrictEqual([24, 48]); + expect(fields[0].values.toArray()).toEqual([1000, 2000]); + expect(fields[1].values.toArray()).toEqual([2, 3]); + expect(fields[2].values.toArray()).toEqual([3, 4]); + expect(fields[3].values.toArray()).toEqual([6, 12]); + expect(fields[4].values.toArray()).toEqual([24, 48]); }); }); diff --git a/public/app/plugins/datasource/loki/responseUtils.ts b/public/app/plugins/datasource/loki/responseUtils.ts index 306c3b387f8..279133c9450 100644 --- a/public/app/plugins/datasource/loki/responseUtils.ts +++ b/public/app/plugins/datasource/loki/responseUtils.ts @@ -220,7 +220,7 @@ function cloneDataFrame(frame: DataQueryResponseData): DataQueryResponseData { ...frame, fields: frame.fields.map((field: Field) => ({ ...field, - values: new ArrayVector(field.values.buffer), + values: new ArrayVector(field.values.toArray()), })), }; } diff --git a/public/app/plugins/datasource/postgres/datasource.test.ts b/public/app/plugins/datasource/postgres/datasource.test.ts index 6a6c0789f35..48c9a2990c9 100644 --- a/public/app/plugins/datasource/postgres/datasource.test.ts +++ b/public/app/plugins/datasource/postgres/datasource.test.ts @@ -141,9 +141,7 @@ describe('PostgreSQLDatasource', () => { entities: {}, name: 'time', type: 'time', - values: { - buffer: [1599643351085], - }, + values: [1599643351085], }, { config: {}, @@ -153,9 +151,7 @@ describe('PostgreSQLDatasource', () => { }, name: 'metric', type: 'number', - values: { - buffer: [30.226249741223704], - }, + values: [30.226249741223704], }, ], length: 1, @@ -237,27 +233,21 @@ describe('PostgreSQLDatasource', () => { entities: {}, name: 'time', type: 'time', - values: { - buffer: [1599643351085], - }, + values: [1599643351085], }, { config: {}, entities: {}, name: 'metric', type: 'string', - values: { - buffer: ['America'], - }, + values: ['America'], }, { config: {}, entities: {}, name: 'value', type: 'number', - values: { - buffer: [30.226249741223704], - }, + values: [30.226249741223704], }, ], length: 1, diff --git a/public/app/plugins/panel/timeseries/utils.ts b/public/app/plugins/panel/timeseries/utils.ts index e2257c6abfc..f40e2dd24e4 100644 --- a/public/app/plugins/panel/timeseries/utils.ts +++ b/public/app/plugins/panel/timeseries/utils.ts @@ -124,14 +124,12 @@ export function prepareGraphableFields( ...field, config, type: FieldType.number, - values: new ArrayVector( - field.values.toArray().map((v) => { - if (v == null) { - return v; - } - return Boolean(v) ? 1 : 0; - }) - ), + values: field.values.map((v) => { + if (v == null) { + return v; + } + return Boolean(v) ? 1 : 0; + }), }; if (!isBooleanUnit(config.unit)) {