mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Transformations: add Concatenate fields transformer (#28237)
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { appendTransformer } from './transformers/append';
|
import { appendTransformer } from './transformers/append';
|
||||||
import { reduceTransformer } from './transformers/reduce';
|
import { reduceTransformer } from './transformers/reduce';
|
||||||
|
import { concatenateTransformer } from './transformers/concat';
|
||||||
import { calculateFieldTransformer } from './transformers/calculateField';
|
import { calculateFieldTransformer } from './transformers/calculateField';
|
||||||
import { filterFieldsTransformer, filterFramesTransformer } from './transformers/filter';
|
import { filterFieldsTransformer, filterFramesTransformer } from './transformers/filter';
|
||||||
import { filterFieldsByNameTransformer } from './transformers/filterByName';
|
import { filterFieldsByNameTransformer } from './transformers/filterByName';
|
||||||
@@ -25,6 +26,7 @@ export const standardTransformers = {
|
|||||||
organizeFieldsTransformer,
|
organizeFieldsTransformer,
|
||||||
appendTransformer,
|
appendTransformer,
|
||||||
reduceTransformer,
|
reduceTransformer,
|
||||||
|
concatenateTransformer,
|
||||||
calculateFieldTransformer,
|
calculateFieldTransformer,
|
||||||
seriesToColumnsTransformer,
|
seriesToColumnsTransformer,
|
||||||
seriesToRowsTransformer,
|
seriesToRowsTransformer,
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
import { toDataFrame } from '../../dataframe/processDataFrame';
|
||||||
|
import { concatenateFields, ConcatenateFrameNameMode } from './concat';
|
||||||
|
|
||||||
|
export const simpleABC = toDataFrame({
|
||||||
|
name: 'ABC',
|
||||||
|
fields: [
|
||||||
|
{ name: 'A', values: [1, 2] },
|
||||||
|
{ name: 'B', values: [1, 2] },
|
||||||
|
{ name: 'C', values: [1, 2] },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const simpleXYZ = toDataFrame({
|
||||||
|
name: 'XYZ',
|
||||||
|
fields: [
|
||||||
|
{ name: 'X', values: [1, 2, 3] },
|
||||||
|
{ name: 'Y', values: [1, 2, 3] },
|
||||||
|
{ name: 'Z', values: [1, 2, 3] },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Concat Transformer', () => {
|
||||||
|
it('dropping frame name', () => {
|
||||||
|
const frame = concatenateFields([simpleABC, simpleXYZ], { frameNameMode: ConcatenateFrameNameMode.Drop });
|
||||||
|
expect(frame.length).toBe(3);
|
||||||
|
expect(frame.fields.map(f => ({ name: f.name, labels: f.labels }))).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"labels": undefined,
|
||||||
|
"name": "A",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"labels": undefined,
|
||||||
|
"name": "B",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"labels": undefined,
|
||||||
|
"name": "C",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"labels": undefined,
|
||||||
|
"name": "X",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"labels": undefined,
|
||||||
|
"name": "Y",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"labels": undefined,
|
||||||
|
"name": "Z",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('using field name', () => {
|
||||||
|
const frame = concatenateFields([simpleABC, simpleXYZ], { frameNameMode: ConcatenateFrameNameMode.FieldName });
|
||||||
|
expect(frame.length).toBe(3);
|
||||||
|
expect(frame.fields.map(f => ({ name: f.name, labels: f.labels }))).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"labels": undefined,
|
||||||
|
"name": "ABC · A",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"labels": undefined,
|
||||||
|
"name": "ABC · B",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"labels": undefined,
|
||||||
|
"name": "ABC · C",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"labels": undefined,
|
||||||
|
"name": "XYZ · X",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"labels": undefined,
|
||||||
|
"name": "XYZ · Y",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"labels": undefined,
|
||||||
|
"name": "XYZ · Z",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('using field label', () => {
|
||||||
|
const frame = concatenateFields([simpleABC, simpleXYZ], {
|
||||||
|
frameNameMode: ConcatenateFrameNameMode.Label,
|
||||||
|
frameNameLabel: 'sensor',
|
||||||
|
});
|
||||||
|
expect(frame.length).toBe(3);
|
||||||
|
expect(frame.fields.map(f => ({ name: f.name, labels: f.labels }))).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"labels": Object {
|
||||||
|
"sensor": "ABC",
|
||||||
|
},
|
||||||
|
"name": "A",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"labels": Object {
|
||||||
|
"sensor": "ABC",
|
||||||
|
},
|
||||||
|
"name": "B",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"labels": Object {
|
||||||
|
"sensor": "ABC",
|
||||||
|
},
|
||||||
|
"name": "C",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"labels": Object {
|
||||||
|
"sensor": "XYZ",
|
||||||
|
},
|
||||||
|
"name": "X",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"labels": Object {
|
||||||
|
"sensor": "XYZ",
|
||||||
|
},
|
||||||
|
"name": "Y",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"labels": Object {
|
||||||
|
"sensor": "XYZ",
|
||||||
|
},
|
||||||
|
"name": "Z",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
105
packages/grafana-data/src/transformations/transformers/concat.ts
Normal file
105
packages/grafana-data/src/transformations/transformers/concat.ts
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { DataTransformerID } from './ids';
|
||||||
|
import { DataTransformerInfo } from '../../types/transformations';
|
||||||
|
import { DataFrame, Field } from '../../types/dataFrame';
|
||||||
|
import { ArrayVector } from '../../vector';
|
||||||
|
|
||||||
|
export enum ConcatenateFrameNameMode {
|
||||||
|
/**
|
||||||
|
* Ignore the source frame name when moving to the destination
|
||||||
|
*/
|
||||||
|
Drop = 'drop',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy the source frame name to the destination field. The final field will contain
|
||||||
|
* both the frame and field name
|
||||||
|
*/
|
||||||
|
FieldName = 'field',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy the source frame name to a label on the field. The label key is controlled
|
||||||
|
* by frameNameLabel
|
||||||
|
*/
|
||||||
|
Label = 'label',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConcatenateTransformerOptions {
|
||||||
|
frameNameMode?: ConcatenateFrameNameMode;
|
||||||
|
frameNameLabel?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const concatenateTransformer: DataTransformerInfo<ConcatenateTransformerOptions> = {
|
||||||
|
id: DataTransformerID.concatenate,
|
||||||
|
name: 'Concatenate fields',
|
||||||
|
description:
|
||||||
|
'Combine all fields into a single frame. Values will be appended with undefined values if not the same length.',
|
||||||
|
defaultOptions: {
|
||||||
|
frameNameMode: ConcatenateFrameNameMode.FieldName,
|
||||||
|
frameNameLabel: 'frame',
|
||||||
|
},
|
||||||
|
operator: options => source =>
|
||||||
|
source.pipe(
|
||||||
|
map(dataFrames => {
|
||||||
|
if (!Array.isArray(dataFrames) || dataFrames.length < 2) {
|
||||||
|
return dataFrames; // noop with single frame
|
||||||
|
}
|
||||||
|
return [concatenateFields(dataFrames, options)];
|
||||||
|
})
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal only exported for tests
|
||||||
|
*/
|
||||||
|
export function concatenateFields(data: DataFrame[], opts: ConcatenateTransformerOptions): DataFrame {
|
||||||
|
let sameLength = true;
|
||||||
|
let maxLength = data[0].length;
|
||||||
|
const frameNameLabel = opts.frameNameLabel ?? 'frame';
|
||||||
|
let fields: Field[] = [];
|
||||||
|
|
||||||
|
for (const frame of data) {
|
||||||
|
if (maxLength !== frame.length) {
|
||||||
|
sameLength = false;
|
||||||
|
maxLength = Math.max(maxLength, frame.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const f of frame.fields) {
|
||||||
|
const copy = { ...f };
|
||||||
|
copy.state = undefined;
|
||||||
|
if (frame.name) {
|
||||||
|
if (opts.frameNameMode === ConcatenateFrameNameMode.Drop) {
|
||||||
|
// nothing -- skip the name
|
||||||
|
} else if (opts.frameNameMode === ConcatenateFrameNameMode.Label) {
|
||||||
|
copy.labels = { ...f.labels };
|
||||||
|
copy.labels[frameNameLabel] = frame.name;
|
||||||
|
} else if (!copy.name || copy.name === 'Value') {
|
||||||
|
copy.name = frame.name;
|
||||||
|
} else {
|
||||||
|
copy.name = `${frame.name} · ${f.name}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fields.push(copy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure all fields have the same length
|
||||||
|
if (!sameLength) {
|
||||||
|
fields = fields.map(f => {
|
||||||
|
if (f.values.length === maxLength) {
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
const values = f.values.toArray();
|
||||||
|
values.length = maxLength;
|
||||||
|
return {
|
||||||
|
...f,
|
||||||
|
values: new ArrayVector(values),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
fields,
|
||||||
|
length: maxLength,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ export enum DataTransformerID {
|
|||||||
seriesToColumns = 'seriesToColumns',
|
seriesToColumns = 'seriesToColumns',
|
||||||
seriesToRows = 'seriesToRows',
|
seriesToRows = 'seriesToRows',
|
||||||
merge = 'merge',
|
merge = 'merge',
|
||||||
|
concatenate = 'concatenate',
|
||||||
labelsToFields = 'labelsToFields',
|
labelsToFields = 'labelsToFields',
|
||||||
filterFields = 'filterFields',
|
filterFields = 'filterFields',
|
||||||
filterFieldsByName = 'filterFieldsByName',
|
filterFieldsByName = 'filterFieldsByName',
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ export abstract class DataSourceApi<
|
|||||||
*
|
*
|
||||||
* Note: `plugin.json` must also define `live: true`
|
* Note: `plugin.json` must also define `live: true`
|
||||||
*
|
*
|
||||||
* @experimental
|
* @alpha -- experimental
|
||||||
*/
|
*/
|
||||||
channelSupport?: LiveChannelSupport;
|
channelSupport?: LiveChannelSupport;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export enum LiveChannelScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @experimental
|
* @alpha -- experimental
|
||||||
*/
|
*/
|
||||||
export interface LiveChannelConfig<TMessage = any> {
|
export interface LiveChannelConfig<TMessage = any> {
|
||||||
/**
|
/**
|
||||||
@@ -69,7 +69,7 @@ export enum LiveChannelEventType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @experimental
|
* @alpha -- experimental
|
||||||
*/
|
*/
|
||||||
export interface LiveChannelStatusEvent {
|
export interface LiveChannelStatusEvent {
|
||||||
type: LiveChannelEventType.Status;
|
type: LiveChannelEventType.Status;
|
||||||
@@ -101,12 +101,12 @@ export interface LiveChannelStatusEvent {
|
|||||||
|
|
||||||
export interface LiveChannelJoinEvent {
|
export interface LiveChannelJoinEvent {
|
||||||
type: LiveChannelEventType.Join;
|
type: LiveChannelEventType.Join;
|
||||||
user: any; // @experimental -- will be filled in when we improve the UI
|
user: any; // @alpha -- experimental -- will be filled in when we improve the UI
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LiveChannelLeaveEvent {
|
export interface LiveChannelLeaveEvent {
|
||||||
type: LiveChannelEventType.Leave;
|
type: LiveChannelEventType.Leave;
|
||||||
user: any; // @experimental -- will be filled in when we improve the UI
|
user: any; // @alpha -- experimental -- will be filled in when we improve the UI
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LiveChannelMessageEvent<T> {
|
export interface LiveChannelMessageEvent<T> {
|
||||||
@@ -137,14 +137,14 @@ export function isLiveChannelMessageEvent<T>(evt: LiveChannelEvent<T>): evt is L
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @experimental
|
* @alpha -- experimental
|
||||||
*/
|
*/
|
||||||
export interface LiveChannelPresenceStatus {
|
export interface LiveChannelPresenceStatus {
|
||||||
users: any; // @experimental -- will be filled in when we improve the UI
|
users: any; // @alpha -- experimental -- will be filled in when we improve the UI
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @experimental
|
* @alpha -- experimental
|
||||||
*/
|
*/
|
||||||
export interface LiveChannelAddress {
|
export interface LiveChannelAddress {
|
||||||
scope: LiveChannelScope;
|
scope: LiveChannelScope;
|
||||||
@@ -160,7 +160,7 @@ export function isValidLiveChannelAddress(addr?: LiveChannelAddress): addr is Li
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @experimental
|
* @alpha -- experimental
|
||||||
*/
|
*/
|
||||||
export interface LiveChannel<TMessage = any, TPublish = any> {
|
export interface LiveChannel<TMessage = any, TPublish = any> {
|
||||||
/** The fully qualified channel id: ${scope}/${namespace}/${path} */
|
/** The fully qualified channel id: ${scope}/${namespace}/${path} */
|
||||||
@@ -201,7 +201,7 @@ export interface LiveChannel<TMessage = any, TPublish = any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @experimental
|
* @alpha -- experimental
|
||||||
*/
|
*/
|
||||||
export interface LiveChannelSupport {
|
export interface LiveChannelSupport {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { LiveChannel, LiveChannelAddress } from '@grafana/data';
|
|||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @experimental
|
* @alpha -- experimental
|
||||||
*/
|
*/
|
||||||
export interface GrafanaLiveSrv {
|
export interface GrafanaLiveSrv {
|
||||||
/**
|
/**
|
||||||
@@ -42,7 +42,7 @@ export const setGrafanaLiveSrv = (instance: GrafanaLiveSrv) => {
|
|||||||
* Used to retrieve the {@link GrafanaLiveSrv} that allows you to subscribe to
|
* Used to retrieve the {@link GrafanaLiveSrv} that allows you to subscribe to
|
||||||
* server side events and streams
|
* server side events and streams
|
||||||
*
|
*
|
||||||
* @experimental
|
* @alpha -- experimental
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export const getGrafanaLiveSrv = (): GrafanaLiveSrv => singletonInstance;
|
export const getGrafanaLiveSrv = (): GrafanaLiveSrv => singletonInstance;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export interface CodeEditorProps {
|
|||||||
/**
|
/**
|
||||||
* Callback after the editor has mounted that gives you raw access to monaco
|
* Callback after the editor has mounted that gives you raw access to monaco
|
||||||
*
|
*
|
||||||
* @experimental - real type is: monaco.editor.IStandaloneCodeEditor
|
* @alpha -- experimental - real type is: monaco.editor.IStandaloneCodeEditor
|
||||||
*/
|
*/
|
||||||
onEditorDidMount?: (editor: any) => void;
|
onEditorDidMount?: (editor: any) => void;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import React, { ChangeEvent } from 'react';
|
||||||
|
import {
|
||||||
|
DataTransformerID,
|
||||||
|
SelectableValue,
|
||||||
|
standardTransformers,
|
||||||
|
TransformerRegistyItem,
|
||||||
|
TransformerUIProps,
|
||||||
|
} from '@grafana/data';
|
||||||
|
import { Input, Select } from '@grafana/ui';
|
||||||
|
import {
|
||||||
|
ConcatenateTransformerOptions,
|
||||||
|
ConcatenateFrameNameMode,
|
||||||
|
} from '@grafana/data/src/transformations/transformers/concat';
|
||||||
|
|
||||||
|
interface ConcatenateTransformerEditorProps extends TransformerUIProps<ConcatenateTransformerOptions> {}
|
||||||
|
|
||||||
|
const nameModes: Array<SelectableValue<ConcatenateFrameNameMode>> = [
|
||||||
|
{ value: ConcatenateFrameNameMode.FieldName, label: 'Copy frame name to field name' },
|
||||||
|
{ value: ConcatenateFrameNameMode.Label, label: 'Add a label with the frame name' },
|
||||||
|
{ value: ConcatenateFrameNameMode.Drop, label: 'Ignore the frame name' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export class ConcatenateTransformerEditor extends React.PureComponent<ConcatenateTransformerEditorProps> {
|
||||||
|
constructor(props: ConcatenateTransformerEditorProps) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
onModeChanged = (value: SelectableValue<ConcatenateFrameNameMode>) => {
|
||||||
|
const { options, onChange } = this.props;
|
||||||
|
const frameNameMode = value.value ?? ConcatenateFrameNameMode.FieldName;
|
||||||
|
onChange({
|
||||||
|
...options,
|
||||||
|
frameNameMode,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onLabelChanged = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const { options } = this.props;
|
||||||
|
this.props.onChange({
|
||||||
|
...options,
|
||||||
|
frameNameLabel: evt.target.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//---------------------------------------------------------
|
||||||
|
// Render
|
||||||
|
//---------------------------------------------------------
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { options } = this.props;
|
||||||
|
|
||||||
|
const frameNameMode = options.frameNameMode ?? ConcatenateFrameNameMode.FieldName;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="gf-form-inline">
|
||||||
|
<div className="gf-form">
|
||||||
|
<div className="gf-form-label width-8">Name</div>
|
||||||
|
<Select
|
||||||
|
className="width-18"
|
||||||
|
options={nameModes}
|
||||||
|
value={nameModes.find(v => v.value === frameNameMode)}
|
||||||
|
onChange={this.onModeChanged}
|
||||||
|
menuPlacement="bottom"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{frameNameMode === ConcatenateFrameNameMode.Label && (
|
||||||
|
<div className="gf-form-inline">
|
||||||
|
<div className="gf-form">
|
||||||
|
<div className="gf-form-label width-8">Label</div>
|
||||||
|
<Input
|
||||||
|
className="width-18"
|
||||||
|
value={options.frameNameLabel ?? ''}
|
||||||
|
placeholder="frame"
|
||||||
|
onChange={this.onLabelChanged}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const concatenateTransformRegistryItem: TransformerRegistyItem<ConcatenateTransformerOptions> = {
|
||||||
|
id: DataTransformerID.concatenate,
|
||||||
|
editor: ConcatenateTransformerEditor,
|
||||||
|
transformation: standardTransformers.concatenateTransformer,
|
||||||
|
name: 'Concatenate fields',
|
||||||
|
description:
|
||||||
|
'Combine all fields into a single frame. Values will be appended with undefined values if not the same length.',
|
||||||
|
};
|
||||||
@@ -9,6 +9,7 @@ import { labelsToFieldsTransformerRegistryItem } from '../components/Transformer
|
|||||||
import { groupByTransformRegistryItem } from '../components/TransformersUI/GroupByTransformerEditor';
|
import { groupByTransformRegistryItem } from '../components/TransformersUI/GroupByTransformerEditor';
|
||||||
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';
|
||||||
|
|
||||||
export const getStandardTransformers = (): Array<TransformerRegistyItem<any>> => {
|
export const getStandardTransformers = (): Array<TransformerRegistyItem<any>> => {
|
||||||
return [
|
return [
|
||||||
@@ -18,6 +19,7 @@ export const getStandardTransformers = (): Array<TransformerRegistyItem<any>> =>
|
|||||||
organizeFieldsTransformRegistryItem,
|
organizeFieldsTransformRegistryItem,
|
||||||
seriesToFieldsTransformerRegistryItem,
|
seriesToFieldsTransformerRegistryItem,
|
||||||
seriesToRowsTransformerRegistryItem,
|
seriesToRowsTransformerRegistryItem,
|
||||||
|
concatenateTransformRegistryItem,
|
||||||
calculateFieldTransformRegistryItem,
|
calculateFieldTransformRegistryItem,
|
||||||
labelsToFieldsTransformerRegistryItem,
|
labelsToFieldsTransformerRegistryItem,
|
||||||
groupByTransformRegistryItem,
|
groupByTransformRegistryItem,
|
||||||
|
|||||||
Reference in New Issue
Block a user