D7net
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
usr
/
share
/
grafana
/
public
/
app
/
features
/
transformers
/
partitionByValues
/
Filename :
partitionByValues.ts
back
Copy
import { map } from 'rxjs'; import { DataFrame, DataTransformerID, SynchronousDataTransformerInfo, getFieldMatcher, DataTransformContext, FieldMatcher, } from '@grafana/data'; import { getMatcherConfig } from '@grafana/data/src/transformations/transformers/filterByName'; import { noopTransformer } from '@grafana/data/src/transformations/transformers/noop'; 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' */ withNames?: boolean; // false /** name/value separator, e.g. '=' in 'Region=Europe' */ separator1?: string; /** name/value pair separator, e.g. ' ' in 'Region=Europe Profession=Chef' */ separator2?: string; } const defaultFrameNameOptions: FrameNamingOptions = { asLabels: true, append: false, withNames: false, separator1: '=', separator2: ' ', }; export interface PartitionByValuesTransformerOptions { /** field names whose values should be used as discriminator keys (typically enum fields) */ 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.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', description: `Splits a one-frame dataset into multiple series discriminated by unique/enum values in one or more fields.`, defaultOptions: {}, operator: (options, ctx) => (source) => source.pipe(map((data) => partitionByValuesTransformer.transformer(options, ctx)(data))), transformer: (options: PartitionByValuesTransformerOptions, ctx: DataTransformContext) => { const matcherConfig = getMatcherConfig({ names: options.fields }); if (!matcherConfig) { return noopTransformer.transformer({}, ctx); } const matcher = getFieldMatcher(matcherConfig); return (data: DataFrame[]) => { if (!data.length) { return data; } // error if > 1 frame? return partitionByValues(data[0], matcher, options); }; }, }; // Split a single frame dataset into multiple frames based on values in a set of fields export function partitionByValues( frame: DataFrame, matcher: FieldMatcher, options?: PartitionByValuesTransformerOptions ): DataFrame[] { const keyFields = frame.fields.filter((f) => matcher(f, frame, [frame]))!; const keyFieldsVals = keyFields.map((f) => f.values); const names = keyFields.map((f) => f.name); const frameNameOpts = { ...defaultFrameNameOptions, ...options?.naming, }; return partition(keyFieldsVals).map((idxs: number[]) => { let frameName = frame.name; let fieldLabels = {}; 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 (frameNameOpts?.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 { name: frameName, meta: frame.meta, length: idxs.length, fields: filteredFields.map((f) => { const vals = f.values; const vals2 = Array(idxs.length); for (let i = 0; i < idxs.length; i++) { vals2[i] = vals[idxs[i]]; } return { name: f.name, type: f.type, config: f.config, labels: { ...f.labels, ...fieldLabels, }, values: vals2, }; }), }; }); }