mirror of
https://github.com/grafana/grafana.git
synced 2025-01-01 11:47:05 -06:00
Transforms: add sort by transformer (#30370)
This commit is contained in:
parent
8c31e25926
commit
bb65113c7d
@ -17,6 +17,7 @@ Grafana comes with the following transformations:
|
||||
- [Add field from calculation](#add-field-from-calculation)
|
||||
- [Labels to fields](#labels-to-fields)
|
||||
- [Concatenate fields](#concatenate-fields)
|
||||
- [Sort by](#sort-by)
|
||||
- [Group by](#group-by)
|
||||
- [Merge](#merge)
|
||||
- [Rename by regex](#rename-by-regex)
|
||||
@ -236,6 +237,14 @@ After merge:
|
||||
| 2020-07-07 11:34:20 | ServerA | 10 | |
|
||||
| 2020-07-07 11:34:20 | | 20 | EU |
|
||||
|
||||
## Sort by
|
||||
|
||||
> **Note:** This transformation is available in Grafana 7.4+.
|
||||
|
||||
This transformation will sort each frame by the configured field, When `reverse` is checked, the values will return in
|
||||
the opposite order.
|
||||
|
||||
|
||||
## Group by
|
||||
|
||||
> **Note:** This transformation is available in Grafana 7.2+.
|
||||
|
@ -13,6 +13,7 @@ import { renameFieldsTransformer } from './transformers/rename';
|
||||
import { labelsToFieldsTransformer } from './transformers/labelsToFields';
|
||||
import { ensureColumnsTransformer } from './transformers/ensureColumns';
|
||||
import { groupByTransformer } from './transformers/groupBy';
|
||||
import { sortByTransformer } from './transformers/sortBy';
|
||||
import { mergeTransformer } from './transformers/merge';
|
||||
import { renameByRegexTransformer } from './transformers/renameByRegex';
|
||||
import { filterByValueTransformer } from './transformers/filterByValue';
|
||||
@ -35,6 +36,7 @@ export const standardTransformers = {
|
||||
labelsToFieldsTransformer,
|
||||
ensureColumnsTransformer,
|
||||
groupByTransformer,
|
||||
sortByTransformer,
|
||||
mergeTransformer,
|
||||
renameByRegexTransformer,
|
||||
};
|
||||
|
@ -21,4 +21,5 @@ export enum DataTransformerID {
|
||||
noop = 'noop',
|
||||
ensureColumns = 'ensureColumns',
|
||||
groupBy = 'groupBy',
|
||||
sortBy = 'sortBy',
|
||||
}
|
||||
|
@ -0,0 +1,99 @@
|
||||
import { toDataFrame } from '../../dataframe/processDataFrame';
|
||||
import { sortByTransformer, SortByTransformerOptions } from './sortBy';
|
||||
import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry';
|
||||
import { transformDataFrame } from '../transformDataFrame';
|
||||
import { Field, FieldType } from '../../types';
|
||||
import { DataTransformerID } from './ids';
|
||||
import { DataTransformerConfig } from '@grafana/data';
|
||||
|
||||
const testFrame = toDataFrame({
|
||||
name: 'A',
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [10, 9, 8, 7, 6, 5] }, // desc
|
||||
{ name: 'text', type: FieldType.string, values: ['a', 'z', 'b', 'x', 'c'] },
|
||||
{ name: 'count', type: FieldType.string, values: [1, 2, 3, 4, 5] }, // asc
|
||||
],
|
||||
});
|
||||
|
||||
describe('SortBy transformer', () => {
|
||||
beforeAll(() => {
|
||||
mockTransformationsRegistry([sortByTransformer]);
|
||||
});
|
||||
|
||||
it('should not apply transformation if config is missing sort fields', async () => {
|
||||
const cfg: DataTransformerConfig<SortByTransformerOptions> = {
|
||||
id: DataTransformerID.sortBy,
|
||||
options: {
|
||||
sort: [], // nothing
|
||||
},
|
||||
};
|
||||
|
||||
await expect(transformDataFrame([cfg], [testFrame])).toEmitValuesWith(received => {
|
||||
const result = received[0];
|
||||
expect(result[0]).toBe(testFrame);
|
||||
});
|
||||
});
|
||||
|
||||
it('should sort time asc', async () => {
|
||||
const cfg: DataTransformerConfig<SortByTransformerOptions> = {
|
||||
id: DataTransformerID.sortBy,
|
||||
options: {
|
||||
sort: [
|
||||
{
|
||||
field: 'time',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
await expect(transformDataFrame([cfg], [testFrame])).toEmitValuesWith(received => {
|
||||
expect(getFieldSnapshot(received[0][0].fields[0])).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"name": "time",
|
||||
"values": Array [
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should sort time (desc)', async () => {
|
||||
const cfg: DataTransformerConfig<SortByTransformerOptions> = {
|
||||
id: DataTransformerID.sortBy,
|
||||
options: {
|
||||
sort: [
|
||||
{
|
||||
field: 'time',
|
||||
desc: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
await expect(transformDataFrame([cfg], [testFrame])).toEmitValuesWith(received => {
|
||||
expect(getFieldSnapshot(received[0][0].fields[0])).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"name": "time",
|
||||
"values": Array [
|
||||
10,
|
||||
9,
|
||||
8,
|
||||
7,
|
||||
6,
|
||||
5,
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getFieldSnapshot(f: Field): Object {
|
||||
return { name: f.name, values: f.values.toArray() };
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { DataTransformerID } from './ids';
|
||||
import { DataTransformerInfo } from '../../types/transformations';
|
||||
import { DataFrame } from '../../types';
|
||||
import { getFieldDisplayName } from '../../field';
|
||||
import { sortDataFrame } from '../../dataframe';
|
||||
|
||||
export interface SortByField {
|
||||
field: string;
|
||||
desc?: boolean;
|
||||
index?: number;
|
||||
}
|
||||
|
||||
export interface SortByTransformerOptions {
|
||||
// NOTE: this structure supports an array, however only the first entry is used
|
||||
// future versions may support multi-sort options
|
||||
sort: SortByField[];
|
||||
}
|
||||
|
||||
export const sortByTransformer: DataTransformerInfo<SortByTransformerOptions> = {
|
||||
id: DataTransformerID.sortBy,
|
||||
name: 'Sort by',
|
||||
description: 'Sort fields in a frame',
|
||||
defaultOptions: {
|
||||
fields: {},
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a modified copy of the series. If the transform is not or should not
|
||||
* be applied, just return the input series
|
||||
*/
|
||||
operator: options => source =>
|
||||
source.pipe(
|
||||
map(data => {
|
||||
if (!Array.isArray(data) || data.length === 0 || !options?.sort?.length) {
|
||||
return data;
|
||||
}
|
||||
return sortDataFrames(data, options.sort);
|
||||
})
|
||||
),
|
||||
};
|
||||
|
||||
export function sortDataFrames(data: DataFrame[], sort: SortByField[]): DataFrame[] {
|
||||
return data.map(frame => {
|
||||
const s = attachFieldIndex(frame, sort);
|
||||
if (s.length && s[0].index != null) {
|
||||
return sortDataFrame(frame, s[0].index, s[0].desc);
|
||||
}
|
||||
return frame;
|
||||
});
|
||||
}
|
||||
|
||||
function attachFieldIndex(frame: DataFrame, sort: SortByField[]): SortByField[] {
|
||||
return sort.map(s => {
|
||||
if (s.index != null) {
|
||||
// null or undefined
|
||||
return s;
|
||||
}
|
||||
return {
|
||||
...s,
|
||||
index: frame.fields.findIndex(f => s.field === getFieldDisplayName(f, frame)),
|
||||
};
|
||||
});
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { DataTransformerID, standardTransformers, TransformerRegistyItem, TransformerUIProps } from '@grafana/data';
|
||||
import { getAllFieldNamesFromDataFrames } from './OrganizeFieldsTransformerEditor';
|
||||
import { InlineField, InlineSwitch, InlineFieldRow, Select } from '@grafana/ui';
|
||||
|
||||
import { SortByField, SortByTransformerOptions } from '@grafana/data/src/transformations/transformers/sortBy';
|
||||
|
||||
export const SortByTransformerEditor: React.FC<TransformerUIProps<SortByTransformerOptions>> = ({
|
||||
input,
|
||||
options,
|
||||
onChange,
|
||||
}) => {
|
||||
const fieldNames = useMemo(
|
||||
() =>
|
||||
getAllFieldNamesFromDataFrames(input).map(n => ({
|
||||
value: n,
|
||||
label: n,
|
||||
})),
|
||||
[input]
|
||||
);
|
||||
|
||||
// Only supports single sort for now
|
||||
const onSortChange = useCallback(
|
||||
(idx: number, cfg: SortByField) => {
|
||||
onChange({ ...options, sort: [cfg] });
|
||||
},
|
||||
[options]
|
||||
);
|
||||
|
||||
const sorts = options.sort?.length ? options.sort : [{} as SortByField];
|
||||
|
||||
return (
|
||||
<div>
|
||||
{sorts.map((s, index) => {
|
||||
return (
|
||||
<InlineFieldRow key={`${s.field}/${index}`}>
|
||||
<InlineField label="Field" labelWidth={10} grow={true}>
|
||||
<Select
|
||||
options={fieldNames}
|
||||
value={fieldNames.find(v => v.value === s.field)}
|
||||
placeholder="Select field"
|
||||
onChange={v => {
|
||||
onSortChange(index, { ...s, field: v.value! });
|
||||
}}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Reverse">
|
||||
<InlineSwitch
|
||||
value={!!s.desc}
|
||||
onChange={() => {
|
||||
onSortChange(index, { ...s, desc: !!!s.desc });
|
||||
}}
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const sortByTransformRegistryItem: TransformerRegistyItem<SortByTransformerOptions> = {
|
||||
id: DataTransformerID.sortBy,
|
||||
editor: SortByTransformerEditor,
|
||||
transformation: standardTransformers.sortByTransformer,
|
||||
name: standardTransformers.sortByTransformer.name,
|
||||
description: standardTransformers.sortByTransformer.description,
|
||||
};
|
@ -8,6 +8,7 @@ import { seriesToFieldsTransformerRegistryItem } from '../components/Transformer
|
||||
import { calculateFieldTransformRegistryItem } from '../components/TransformersUI/CalculateFieldTransformerEditor';
|
||||
import { labelsToFieldsTransformerRegistryItem } from '../components/TransformersUI/LabelsToFieldsTransformerEditor';
|
||||
import { groupByTransformRegistryItem } from '../components/TransformersUI/GroupByTransformerEditor';
|
||||
import { sortByTransformRegistryItem } from '../components/TransformersUI/SortByTransformerEditor';
|
||||
import { mergeTransformerRegistryItem } from '../components/TransformersUI/MergeTransformerEditor';
|
||||
import { seriesToRowsTransformerRegistryItem } from '../components/TransformersUI/SeriesToRowsTransformerEditor';
|
||||
import { concatenateTransformRegistryItem } from '../components/TransformersUI/ConcatenateTransformerEditor';
|
||||
@ -27,6 +28,7 @@ export const getStandardTransformers = (): Array<TransformerRegistyItem<any>> =>
|
||||
calculateFieldTransformRegistryItem,
|
||||
labelsToFieldsTransformerRegistryItem,
|
||||
groupByTransformRegistryItem,
|
||||
sortByTransformRegistryItem,
|
||||
mergeTransformerRegistryItem,
|
||||
];
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user