Transforms: make partitionByValues create field labels by default (#61255)

This commit is contained in:
Leon Sorokin
2023-01-17 14:56:23 -06:00
committed by GitHub
parent 05a683d462
commit 7e7daf48f4
2 changed files with 138 additions and 16 deletions

View File

@@ -21,6 +21,10 @@ describe('Partition by values transformer', () => {
const config: PartitionByValuesTransformerOptions = {
fields: ['region'],
keepFields: true,
naming: {
asLabels: false,
},
};
let partitioned = partitionByValuesTransformer.transformer(config, ctx)(source);
@@ -57,6 +61,10 @@ describe('Partition by values transformer', () => {
const config: PartitionByValuesTransformerOptions = {
fields: ['region', 'status'],
keepFields: true,
naming: {
asLabels: false,
},
};
let partitioned = partitionByValuesTransformer.transformer(config, ctx)(source);
@@ -100,7 +108,7 @@ describe('Partition by values transformer', () => {
expect(partitioned[3].fields[2].values.toArray()).toEqual(['FAIL']);
});
it('should partition by multiple fields with custom frame naming {withFields: true}', () => {
it('should partition by multiple fields with custom frame naming {withNames: true}', () => {
const source = [
toDataFrame({
name: 'XYZ',
@@ -115,8 +123,10 @@ describe('Partition by values transformer', () => {
const config: PartitionByValuesTransformerOptions = {
fields: ['region', 'status'],
keepFields: true,
naming: {
withFields: true,
asLabels: false,
withNames: true,
},
};
@@ -143,7 +153,9 @@ describe('Partition by values transformer', () => {
const config: PartitionByValuesTransformerOptions = {
fields: ['region', 'status'],
keepFields: true,
naming: {
asLabels: false,
append: true,
},
};
@@ -156,7 +168,7 @@ describe('Partition by values transformer', () => {
expect(partitioned[3].name).toEqual('XYZ China FAIL');
});
it('should partition by multiple fields with custom frame naming {withFields: true, append: true}', () => {
it('should partition by multiple fields with custom frame naming {withNames: true, append: true}', () => {
const source = [
toDataFrame({
name: 'XYZ',
@@ -171,8 +183,10 @@ describe('Partition by values transformer', () => {
const config: PartitionByValuesTransformerOptions = {
fields: ['region', 'status'],
keepFields: true,
naming: {
withFields: true,
asLabels: false,
withNames: true,
append: true,
},
};
@@ -184,4 +198,70 @@ describe('Partition by values transformer', () => {
expect(partitioned[2].name).toEqual('XYZ region=China status=OK');
expect(partitioned[3].name).toEqual('XYZ region=China status=FAIL');
});
it('should partition by multiple fields naming: {asLabels: true}', () => {
const source = [
toDataFrame({
name: 'XYZ',
refId: 'A',
fields: [
{ name: 'model', type: FieldType.string, values: ['E1', 'E2', 'C1', 'E3', 'C2', 'C3'] },
{ name: 'region', type: FieldType.string, values: ['Europe', 'Europe', 'China', 'Europe', 'China', 'China'] },
{ name: 'status', type: FieldType.string, values: ['OK', 'FAIL', 'OK', 'FAIL', 'OK', 'FAIL'] },
],
}),
];
const config: PartitionByValuesTransformerOptions = {
fields: ['region', 'status'],
keepFields: true,
naming: {
asLabels: true,
},
};
let partitioned = partitionByValuesTransformer.transformer(config, ctx)(source);
// all frame names are same
expect(partitioned[0].name).toEqual('XYZ');
expect(partitioned[1].name).toEqual('XYZ');
expect(partitioned[2].name).toEqual('XYZ');
expect(partitioned[3].name).toEqual('XYZ');
// all frames contain all fields
expect(partitioned[0].fields[0].name).toEqual('model');
expect(partitioned[0].fields[1].name).toEqual('region');
expect(partitioned[0].fields[2].name).toEqual('status');
// in each frame, every field has same labels
expect(partitioned[0].fields[0].labels).toEqual({ region: 'Europe', status: 'OK' });
expect(partitioned[1].fields[0].labels).toEqual({ region: 'Europe', status: 'FAIL' });
expect(partitioned[2].fields[0].labels).toEqual({ region: 'China', status: 'OK' });
expect(partitioned[3].fields[0].labels).toEqual({ region: 'China', status: 'FAIL' });
});
it('should partition by multiple fields and omit those fields in result', () => {
const source = [
toDataFrame({
name: 'XYZ',
refId: 'A',
fields: [
{ name: 'model', type: FieldType.string, values: ['E1', 'E2', 'C1', 'E3', 'C2', 'C3'] },
{ name: 'region', type: FieldType.string, values: ['Europe', 'Europe', 'China', 'Europe', 'China', 'China'] },
{ name: 'status', type: FieldType.string, values: ['OK', 'FAIL', 'OK', 'FAIL', 'OK', 'FAIL'] },
],
}),
];
const config: PartitionByValuesTransformerOptions = {
fields: ['region', 'status'],
};
let partitioned = partitionByValuesTransformer.transformer(config, ctx)(source);
// all frames contain only model field
expect(partitioned[0].fields.length).toEqual(1);
expect(partitioned[0].fields[0].name).toEqual('model');
expect(partitioned[0].fields[0].labels).toEqual({ region: 'Europe', status: 'OK' });
});
});

View File

@@ -14,10 +14,15 @@ import { noopTransformer } from '@grafana/data/src/transformations/transformers/
import { partition } from './partition';
export interface FrameNamingOptions {
/** when true, the frame name is copied unmodified, and discriminator fields' names+values become field labels in new frames */
asLabels?: boolean;
/** opts below are used only when asLabels: false */
/** whether to append to existing frame name, false -> replace */
append?: boolean; // false
/** whether to include discriminator field names, e.g. true -> Region=Europe Profession=Chef, false -> 'Europe Chef' */
withFields?: boolean; // false
withNames?: boolean; // false
/** name/value separator, e.g. '=' in 'Region=Europe' */
separator1?: string;
/** name/value pair separator, e.g. ' ' in 'Region=Europe Profession=Chef' */
@@ -25,8 +30,10 @@ export interface FrameNamingOptions {
}
const defaultFrameNameOptions: FrameNamingOptions = {
asLabels: true,
append: false,
withFields: false,
withNames: false,
separator1: '=',
separator2: ' ',
};
@@ -36,14 +43,26 @@ export interface PartitionByValuesTransformerOptions {
fields: string[];
/** how the split frames' names should be suffixed (ends up as field prefixes) */
naming?: FrameNamingOptions;
/** should the discriminator fields be kept in the output */
keepFields?: boolean;
}
function buildFrameName(opts: FrameNamingOptions, names: string[], values: unknown[]): string {
return names
.map((name, i) => (opts.withFields ? `${name}${opts.separator1}${values[i]}` : values[i]))
.map((name, i) => (opts.withNames ? `${name}${opts.separator1}${values[i]}` : values[i]))
.join(opts.separator2);
}
function buildFieldLabels(names: string[], values: unknown[]) {
const labels: Record<string, string> = {};
names.forEach((name, i) => {
labels[name] = String(values[i]);
});
return labels;
}
export const partitionByValuesTransformer: SynchronousDataTransformerInfo<PartitionByValuesTransformerOptions> = {
id: DataTransformerID.partitionByValues,
name: 'Partition by values',
@@ -78,21 +97,40 @@ export const partitionByValuesTransformer: SynchronousDataTransformerInfo<Partit
};
return partition(keyFieldsVals).map((idxs: number[]) => {
let name = buildFrameName(
frameNameOpts,
names,
keyFields.map((f, i) => keyFieldsVals[i][idxs[0]])
);
let frameName = frame.name;
let fieldLabels = {};
if (options.naming?.append && frame.name) {
name = `${frame.name} ${name}`;
if (frameNameOpts.asLabels) {
fieldLabels = buildFieldLabels(
names,
keyFields.map((f, i) => keyFieldsVals[i][idxs[0]])
);
} else {
let name = buildFrameName(
frameNameOpts,
names,
keyFields.map((f, i) => keyFieldsVals[i][idxs[0]])
);
if (options.naming?.append && frame.name) {
name = `${frame.name} ${name}`;
}
frameName = name;
}
let filteredFields = frame.fields;
if (!options.keepFields) {
const keyFieldNames = new Set(names);
filteredFields = frame.fields.filter((field) => !keyFieldNames.has(field.name));
}
return {
...frame,
name,
name: frameName,
length: idxs.length,
fields: frame.fields.map((f) => {
fields: filteredFields.map((f) => {
const vals = f.values.toArray();
const vals2 = Array(idxs.length);
@@ -102,6 +140,10 @@ export const partitionByValuesTransformer: SynchronousDataTransformerInfo<Partit
return {
...f,
labels: {
...f.labels,
...fieldLabels,
},
state: undefined,
values: new ArrayVector(vals2),
};