mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Transforms: make partitionByValues create field labels by default (#61255)
This commit is contained in:
@@ -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' });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user