Transform: Add a limit transform (#49291)

* Add a limit transform

* Add a limit transform

* Simplify filter code

Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
This commit is contained in:
Josiah (Jay) Goodson 2022-08-10 10:21:11 -04:00 committed by GitHub
parent 062d255124
commit 7d7890e23c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 222 additions and 1 deletions

View File

@ -2,5 +2,5 @@
# Manual changes might be lost!
integrations:
- vscode
- vim
- vscode

View File

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

View File

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

View File

@ -33,4 +33,5 @@ export enum DataTransformerID {
joinByLabels = 'joinByLabels',
extractFields = 'extractFields',
groupingToMatrix = 'groupingToMatrix',
limit = 'limit',
}

View File

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

View File

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

View File

@ -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<TransformerUIProps<LimitTransformerOptions>> = ({
options,
onChange,
}) => {
const onSetLimit = useCallback(
(value: FormEvent<HTMLInputElement>) => {
onChange({
...options,
limitField: Number(value.currentTarget.value),
});
},
[onChange, options]
);
return (
<>
<InlineFieldRow>
<InlineField label="Limit" labelWidth={8}>
<Input
placeholder="Limit count"
pattern="[0-9]*"
value={options.limitField}
onChange={onSetLimit}
width={25}
/>
</InlineField>
</InlineFieldRow>
</>
);
};
export const limitTransformRegistryItem: TransformerRegistryItem<LimitTransformerOptions> = {
id: DataTransformerID.limit,
editor: LimitTransformerEditor,
transformation: standardTransformers.limitTransformer,
name: 'Limit',
description: `Limit the number of items displayed.`,
};

View File

@ -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<TransformerRegistryItem<any>> =
extractFieldsTransformRegistryItem,
heatmapTransformRegistryItem,
groupingToMatrixTransformRegistryItem,
limitTransformRegistryItem,
joinByLabelsTransformRegistryItem,
];
};