Transforms: add sort by transformer (#30370)

This commit is contained in:
Ryan McKinley
2021-01-18 23:01:38 -08:00
committed by GitHub
parent 8c31e25926
commit bb65113c7d
7 changed files with 246 additions and 0 deletions

View File

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

View File

@@ -21,4 +21,5 @@ export enum DataTransformerID {
noop = 'noop',
ensureColumns = 'ensureColumns',
groupBy = 'groupBy',
sortBy = 'sortBy',
}

View File

@@ -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() };
}

View File

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