diff --git a/.yarn/sdks/integrations.yml b/.yarn/sdks/integrations.yml index 7e42d1884d8..401be99859b 100644 --- a/.yarn/sdks/integrations.yml +++ b/.yarn/sdks/integrations.yml @@ -2,5 +2,5 @@ # Manual changes might be lost! integrations: - - vscode - vim + - vscode diff --git a/docs/sources/panels/transform-data/transformation-functions.md b/docs/sources/panels/transform-data/transformation-functions.md index b478add76a7..0da8bbca552 100644 --- a/docs/sources/panels/transform-data/transformation-functions.md +++ b/docs/sources/panels/transform-data/transformation-functions.md @@ -545,3 +545,26 @@ Here is the result after applying the Series to rows transformation. ## Sort by This transformation will sort each frame by the configured field, When `reverse` is checked, the values will return in the opposite order. + +## Limit + +Use this transformation to limit the number of rows displayed. + +In the example below, we have the following response from the data source: + +| Time | Metric | Value | +| ------------------- | ----------- | ----- | +| 2020-07-07 11:34:20 | Temperature | 25 | +| 2020-07-07 11:34:20 | Humidity | 22 | +| 2020-07-07 10:32:20 | Humidity | 29 | +| 2020-07-07 10:31:22 | Temperature | 22 | +| 2020-07-07 09:30:57 | Humidity | 33 | +| 2020-07-07 09:30:05 | Temperature | 19 | + +Here is the result after adding a Limit transformation with a value of '3': + +| Time | Metric | Value | +| ------------------- | ----------- | ----- | +| 2020-07-07 11:34:20 | Temperature | 25 | +| 2020-07-07 11:34:20 | Humidity | 22 | +| 2020-07-07 10:32:20 | Humidity | 29 | diff --git a/packages/grafana-data/src/transformations/transformers.ts b/packages/grafana-data/src/transformations/transformers.ts index 3836a6929da..49f13475292 100644 --- a/packages/grafana-data/src/transformations/transformers.ts +++ b/packages/grafana-data/src/transformations/transformers.ts @@ -10,6 +10,7 @@ import { groupByTransformer } from './transformers/groupBy'; import { groupingToMatrixTransformer } from './transformers/groupingToMatrix'; import { histogramTransformer } from './transformers/histogram'; import { labelsToFieldsTransformer } from './transformers/labelsToFields'; +import { limitTransformer } from './transformers/limit'; import { mergeTransformer } from './transformers/merge'; import { noopTransformer } from './transformers/noop'; import { orderFieldsTransformer } from './transformers/order'; @@ -45,4 +46,5 @@ export const standardTransformers = { histogramTransformer, convertFieldTypeTransformer, groupingToMatrixTransformer, + limitTransformer, }; diff --git a/packages/grafana-data/src/transformations/transformers/ids.ts b/packages/grafana-data/src/transformations/transformers/ids.ts index 0c2b4104487..a4805824abe 100644 --- a/packages/grafana-data/src/transformations/transformers/ids.ts +++ b/packages/grafana-data/src/transformations/transformers/ids.ts @@ -33,4 +33,5 @@ export enum DataTransformerID { joinByLabels = 'joinByLabels', extractFields = 'extractFields', groupingToMatrix = 'groupingToMatrix', + limit = 'limit', } diff --git a/packages/grafana-data/src/transformations/transformers/limit.test.ts b/packages/grafana-data/src/transformations/transformers/limit.test.ts new file mode 100644 index 00000000000..93228e70523 --- /dev/null +++ b/packages/grafana-data/src/transformations/transformers/limit.test.ts @@ -0,0 +1,104 @@ +import { DataTransformerConfig } from '@grafana/data'; + +import { toDataFrame } from '../../dataframe/processDataFrame'; +import { Field, FieldType } from '../../types'; +import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; +import { ArrayVector } from '../../vector'; +import { transformDataFrame } from '../transformDataFrame'; + +import { DataTransformerID } from './ids'; +import { limitTransformer, LimitTransformerOptions } from './limit'; + +describe('Limit transformer', () => { + beforeAll(() => { + mockTransformationsRegistry([limitTransformer]); + }); + + it('should limit the number of items', async () => { + const testSeries = toDataFrame({ + name: 'A', + fields: [ + { name: 'time', type: FieldType.time, values: [3000, 4000, 5000, 6000, 7000, 8000] }, + { name: 'message', type: FieldType.string, values: ['one', 'two', 'two', 'three', 'three', 'three'] }, + { name: 'values', type: FieldType.number, values: [1, 2, 2, 3, 3, 3] }, + ], + }); + + const cfg: DataTransformerConfig = { + id: DataTransformerID.limit, + options: { + limitField: 3, + }, + }; + + await expect(transformDataFrame([cfg], [testSeries])).toEmitValuesWith((received) => { + const result = received[0]; + const expected: Field[] = [ + { + name: 'time', + type: FieldType.time, + values: new ArrayVector([3000, 4000, 5000]), + config: {}, + }, + { + name: 'message', + type: FieldType.string, + values: new ArrayVector(['one', 'two', 'two']), + config: {}, + }, + { + name: 'values', + type: FieldType.number, + values: new ArrayVector([1, 2, 2]), + config: {}, + }, + ]; + + expect(result[0].fields).toEqual(expected); + }); + }); + + it('should not limit the number of items if limit > number of items', async () => { + const testSeries = toDataFrame({ + name: 'A', + fields: [ + { name: 'time', type: FieldType.time, values: [3000, 4000, 5000, 6000, 7000, 8000] }, + { name: 'message', type: FieldType.string, values: ['one', 'two', 'two', 'three', 'three', 'three'] }, + { name: 'values', type: FieldType.number, values: [1, 2, 2, 3, 3, 3] }, + ], + }); + + const cfg: DataTransformerConfig = { + id: DataTransformerID.limit, + options: { + limitField: 7, + }, + }; + + await expect(transformDataFrame([cfg], [testSeries])).toEmitValuesWith((received) => { + const result = received[0]; + const expected: Field[] = [ + { + name: 'time', + type: FieldType.time, + values: new ArrayVector([3000, 4000, 5000, 6000, 7000, 8000]), + config: {}, + }, + { + name: 'message', + type: FieldType.string, + values: new ArrayVector(['one', 'two', 'two', 'three', 'three', 'three']), + config: {}, + }, + { + name: 'values', + type: FieldType.number, + values: new ArrayVector([1, 2, 2, 3, 3, 3]), + config: {}, + }, + ]; + + expect(result[0].fields).toEqual(expected); + }); + }); +}); diff --git a/packages/grafana-data/src/transformations/transformers/limit.ts b/packages/grafana-data/src/transformations/transformers/limit.ts new file mode 100644 index 00000000000..f0bfd04fa6c --- /dev/null +++ b/packages/grafana-data/src/transformations/transformers/limit.ts @@ -0,0 +1,45 @@ +import { map } from 'rxjs/operators'; + +import { DataTransformerInfo } from '../../types'; +import { ArrayVector } from '../../vector/ArrayVector'; + +import { DataTransformerID } from './ids'; + +export interface LimitTransformerOptions { + limitField?: number; +} + +const DEFAULT_LIMIT_FIELD = 10; + +export const limitTransformer: DataTransformerInfo = { + id: DataTransformerID.limit, + name: 'Limit', + description: 'Limit the number of items to the top N', + defaultOptions: { + limitField: DEFAULT_LIMIT_FIELD, + }, + + operator: (options) => (source) => + source.pipe( + map((data) => { + const limitFieldMatch = options.limitField || DEFAULT_LIMIT_FIELD; + return data.map((frame) => { + if (frame.length > limitFieldMatch) { + return { + ...frame, + fields: frame.fields.map((f) => { + const vals = f.values.toArray(); + return { + ...f, + values: new ArrayVector(vals.slice(0, limitFieldMatch)), + }; + }), + length: limitFieldMatch, + }; + } + + return frame; + }); + }) + ), +}; diff --git a/public/app/features/transformers/editors/LimitTransformerEditor.tsx b/public/app/features/transformers/editors/LimitTransformerEditor.tsx new file mode 100644 index 00000000000..3cd80da1e14 --- /dev/null +++ b/public/app/features/transformers/editors/LimitTransformerEditor.tsx @@ -0,0 +1,44 @@ +import React, { FormEvent, useCallback } from 'react'; + +import { DataTransformerID, standardTransformers, TransformerRegistryItem, TransformerUIProps } from '@grafana/data'; +import { LimitTransformerOptions } from '@grafana/data/src/transformations/transformers/limit'; +import { InlineField, InlineFieldRow, Input } from '@grafana/ui'; + +export const LimitTransformerEditor: React.FC> = ({ + options, + onChange, +}) => { + const onSetLimit = useCallback( + (value: FormEvent) => { + onChange({ + ...options, + limitField: Number(value.currentTarget.value), + }); + }, + [onChange, options] + ); + + return ( + <> + + + + + + + ); +}; + +export const limitTransformRegistryItem: TransformerRegistryItem = { + id: DataTransformerID.limit, + editor: LimitTransformerEditor, + transformation: standardTransformers.limitTransformer, + name: 'Limit', + description: `Limit the number of items displayed.`, +}; diff --git a/public/app/features/transformers/standardTransformers.ts b/public/app/features/transformers/standardTransformers.ts index 62f4e08180f..9db127171f5 100644 --- a/public/app/features/transformers/standardTransformers.ts +++ b/public/app/features/transformers/standardTransformers.ts @@ -12,6 +12,7 @@ import { groupByTransformRegistryItem } from './editors/GroupByTransformerEditor import { groupingToMatrixTransformRegistryItem } from './editors/GroupingToMatrixTransformerEditor'; import { histogramTransformRegistryItem } from './editors/HistogramTransformerEditor'; import { labelsToFieldsTransformerRegistryItem } from './editors/LabelsToFieldsTransformerEditor'; +import { limitTransformRegistryItem } from './editors/LimitTransformerEditor'; import { mergeTransformerRegistryItem } from './editors/MergeTransformerEditor'; import { organizeFieldsTransformRegistryItem } from './editors/OrganizeFieldsTransformerEditor'; import { reduceTransformRegistryItem } from './editors/ReduceTransformerEditor'; @@ -52,6 +53,7 @@ export const getStandardTransformers = (): Array> = extractFieldsTransformRegistryItem, heatmapTransformRegistryItem, groupingToMatrixTransformRegistryItem, + limitTransformRegistryItem, joinByLabelsTransformRegistryItem, ]; };