mirror of
https://github.com/grafana/grafana.git
synced 2025-09-23 02:43:03 +08:00
Transforms: add sort by transformer (#30370)
This commit is contained in:
@ -17,6 +17,7 @@ Grafana comes with the following transformations:
|
|||||||
- [Add field from calculation](#add-field-from-calculation)
|
- [Add field from calculation](#add-field-from-calculation)
|
||||||
- [Labels to fields](#labels-to-fields)
|
- [Labels to fields](#labels-to-fields)
|
||||||
- [Concatenate fields](#concatenate-fields)
|
- [Concatenate fields](#concatenate-fields)
|
||||||
|
- [Sort by](#sort-by)
|
||||||
- [Group by](#group-by)
|
- [Group by](#group-by)
|
||||||
- [Merge](#merge)
|
- [Merge](#merge)
|
||||||
- [Rename by regex](#rename-by-regex)
|
- [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 | ServerA | 10 | |
|
||||||
| 2020-07-07 11:34:20 | | 20 | EU |
|
| 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
|
## Group by
|
||||||
|
|
||||||
> **Note:** This transformation is available in Grafana 7.2+.
|
> **Note:** This transformation is available in Grafana 7.2+.
|
||||||
|
@ -13,6 +13,7 @@ import { renameFieldsTransformer } from './transformers/rename';
|
|||||||
import { labelsToFieldsTransformer } from './transformers/labelsToFields';
|
import { labelsToFieldsTransformer } from './transformers/labelsToFields';
|
||||||
import { ensureColumnsTransformer } from './transformers/ensureColumns';
|
import { ensureColumnsTransformer } from './transformers/ensureColumns';
|
||||||
import { groupByTransformer } from './transformers/groupBy';
|
import { groupByTransformer } from './transformers/groupBy';
|
||||||
|
import { sortByTransformer } from './transformers/sortBy';
|
||||||
import { mergeTransformer } from './transformers/merge';
|
import { mergeTransformer } from './transformers/merge';
|
||||||
import { renameByRegexTransformer } from './transformers/renameByRegex';
|
import { renameByRegexTransformer } from './transformers/renameByRegex';
|
||||||
import { filterByValueTransformer } from './transformers/filterByValue';
|
import { filterByValueTransformer } from './transformers/filterByValue';
|
||||||
@ -35,6 +36,7 @@ export const standardTransformers = {
|
|||||||
labelsToFieldsTransformer,
|
labelsToFieldsTransformer,
|
||||||
ensureColumnsTransformer,
|
ensureColumnsTransformer,
|
||||||
groupByTransformer,
|
groupByTransformer,
|
||||||
|
sortByTransformer,
|
||||||
mergeTransformer,
|
mergeTransformer,
|
||||||
renameByRegexTransformer,
|
renameByRegexTransformer,
|
||||||
};
|
};
|
||||||
|
@ -21,4 +21,5 @@ export enum DataTransformerID {
|
|||||||
noop = 'noop',
|
noop = 'noop',
|
||||||
ensureColumns = 'ensureColumns',
|
ensureColumns = 'ensureColumns',
|
||||||
groupBy = 'groupBy',
|
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 { calculateFieldTransformRegistryItem } from '../components/TransformersUI/CalculateFieldTransformerEditor';
|
||||||
import { labelsToFieldsTransformerRegistryItem } from '../components/TransformersUI/LabelsToFieldsTransformerEditor';
|
import { labelsToFieldsTransformerRegistryItem } from '../components/TransformersUI/LabelsToFieldsTransformerEditor';
|
||||||
import { groupByTransformRegistryItem } from '../components/TransformersUI/GroupByTransformerEditor';
|
import { groupByTransformRegistryItem } from '../components/TransformersUI/GroupByTransformerEditor';
|
||||||
|
import { sortByTransformRegistryItem } from '../components/TransformersUI/SortByTransformerEditor';
|
||||||
import { mergeTransformerRegistryItem } from '../components/TransformersUI/MergeTransformerEditor';
|
import { mergeTransformerRegistryItem } from '../components/TransformersUI/MergeTransformerEditor';
|
||||||
import { seriesToRowsTransformerRegistryItem } from '../components/TransformersUI/SeriesToRowsTransformerEditor';
|
import { seriesToRowsTransformerRegistryItem } from '../components/TransformersUI/SeriesToRowsTransformerEditor';
|
||||||
import { concatenateTransformRegistryItem } from '../components/TransformersUI/ConcatenateTransformerEditor';
|
import { concatenateTransformRegistryItem } from '../components/TransformersUI/ConcatenateTransformerEditor';
|
||||||
@ -27,6 +28,7 @@ export const getStandardTransformers = (): Array<TransformerRegistyItem<any>> =>
|
|||||||
calculateFieldTransformRegistryItem,
|
calculateFieldTransformRegistryItem,
|
||||||
labelsToFieldsTransformerRegistryItem,
|
labelsToFieldsTransformerRegistryItem,
|
||||||
groupByTransformRegistryItem,
|
groupByTransformRegistryItem,
|
||||||
|
sortByTransformRegistryItem,
|
||||||
mergeTransformerRegistryItem,
|
mergeTransformerRegistryItem,
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user