mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Transformations: Refactor to use single registry for transformations (#23502)
* Use single registry for transformations * Fix transformations tests * Added documentation comments and minor refactor * Added documentation comments and minor refactor Minor misunderstanding between me and Typescript. We should be good friends back now. * Fix registry import
This commit is contained in:
parent
7f61f3cc43
commit
58257a95a3
@ -1,10 +1,16 @@
|
|||||||
export * from './matchers/ids';
|
export * from './matchers/ids';
|
||||||
export * from './transformers/ids';
|
export * from './transformers/ids';
|
||||||
export * from './matchers';
|
export * from './matchers';
|
||||||
export * from './transformers';
|
export { standardTransformers } from './transformers';
|
||||||
export * from './fieldReducer';
|
export * from './fieldReducer';
|
||||||
export { FilterFieldsByNameTransformerOptions } from './transformers/filterByName';
|
export { FilterFieldsByNameTransformerOptions } from './transformers/filterByName';
|
||||||
export { FilterFramesByRefIdTransformerOptions } from './transformers/filterByRefId';
|
export { FilterFramesByRefIdTransformerOptions } from './transformers/filterByRefId';
|
||||||
export { ReduceTransformerOptions } from './transformers/reduce';
|
export { ReduceTransformerOptions } from './transformers/reduce';
|
||||||
export { OrganizeFieldsTransformerOptions } from './transformers/organize';
|
export { OrganizeFieldsTransformerOptions } from './transformers/organize';
|
||||||
export { createOrderFieldsComparer } from './transformers/order';
|
export { createOrderFieldsComparer } from './transformers/order';
|
||||||
|
export { transformDataFrame } from './transformDataFrame';
|
||||||
|
export {
|
||||||
|
TransformerRegistyItem,
|
||||||
|
TransformerUIProps,
|
||||||
|
standardTransformersRegistry,
|
||||||
|
} from './standardTransformersRegistry';
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { DataFrame, DataTransformerInfo } from '../types';
|
||||||
|
import { Registry, RegistryItem } from '../utils/Registry';
|
||||||
|
|
||||||
|
export interface TransformerUIProps<T> {
|
||||||
|
/**
|
||||||
|
* Transformer configuration, persisted on panel's model
|
||||||
|
*/
|
||||||
|
options: T;
|
||||||
|
/**
|
||||||
|
* Pre-transform data rames
|
||||||
|
*/
|
||||||
|
input: DataFrame[];
|
||||||
|
onChange: (options: T) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransformerRegistyItem<TOptions> extends RegistryItem {
|
||||||
|
/**
|
||||||
|
* Object describing transformer configuration
|
||||||
|
*/
|
||||||
|
transformation: DataTransformerInfo<TOptions>;
|
||||||
|
/**
|
||||||
|
* React component used as UI for the transformer
|
||||||
|
*/
|
||||||
|
editor: React.ComponentType<TransformerUIProps<TOptions>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registry of transformation options that can be driven by
|
||||||
|
* stored configuration files.
|
||||||
|
*/
|
||||||
|
export const standardTransformersRegistry = new Registry<TransformerRegistyItem<any>>();
|
@ -0,0 +1,35 @@
|
|||||||
|
import { DataFrame, DataTransformerConfig } from '../types';
|
||||||
|
import { standardTransformersRegistry } from './standardTransformersRegistry';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply configured transformations to the input data
|
||||||
|
*/
|
||||||
|
export function transformDataFrame(options: DataTransformerConfig[], data: DataFrame[]): DataFrame[] {
|
||||||
|
let processed = data;
|
||||||
|
for (const config of options) {
|
||||||
|
const info = standardTransformersRegistry.get(config.id);
|
||||||
|
|
||||||
|
if (!info) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const transformer = info.transformation.transformer(config.options);
|
||||||
|
const after = transformer(processed);
|
||||||
|
|
||||||
|
// Add a key to the metadata if the data changed
|
||||||
|
if (after && after !== processed) {
|
||||||
|
for (const series of after) {
|
||||||
|
if (!series.meta) {
|
||||||
|
series.meta = {};
|
||||||
|
}
|
||||||
|
if (!series.meta.transformations) {
|
||||||
|
series.meta.transformations = [info.id];
|
||||||
|
} else {
|
||||||
|
series.meta.transformations = [...series.meta.transformations, info.id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
processed = after;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return processed;
|
||||||
|
}
|
@ -1,34 +0,0 @@
|
|||||||
import { DataTransformerID } from './transformers/ids';
|
|
||||||
import { transformersRegistry } from './transformers';
|
|
||||||
import { toDataFrame } from '../dataframe/processDataFrame';
|
|
||||||
import { ReducerID } from './fieldReducer';
|
|
||||||
import { DataFrameView } from '../dataframe/DataFrameView';
|
|
||||||
|
|
||||||
describe('Transformers', () => {
|
|
||||||
it('should load all transformeres', () => {
|
|
||||||
for (const name of Object.keys(DataTransformerID)) {
|
|
||||||
const calc = transformersRegistry.get(name);
|
|
||||||
expect(calc.id).toBe(name);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const seriesWithValues = toDataFrame({
|
|
||||||
fields: [
|
|
||||||
{ name: 'A', values: [1, 2, 3, 4] }, // Numbers
|
|
||||||
{ name: 'B', values: ['a', 'b', 'c', 'd'] }, // Strings
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use fluent API', () => {
|
|
||||||
const results = transformersRegistry.reduce([seriesWithValues], {
|
|
||||||
reducers: [ReducerID.first],
|
|
||||||
});
|
|
||||||
expect(results.length).toBe(1);
|
|
||||||
|
|
||||||
const view = new DataFrameView(results[0]).toJSON();
|
|
||||||
expect(view).toEqual([
|
|
||||||
{ Field: 'A', first: 1 }, // Row 0
|
|
||||||
{ Field: 'B', first: 'a' }, // Row 1
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,70 +1,15 @@
|
|||||||
import { DataFrame } from '../types/dataFrame';
|
import { appendTransformer } from './transformers/append';
|
||||||
import { Registry } from '../utils/Registry';
|
import { reduceTransformer } from './transformers/reduce';
|
||||||
import { AppendOptions, appendTransformer } from './transformers/append';
|
|
||||||
import { reduceTransformer, ReduceTransformerOptions } from './transformers/reduce';
|
|
||||||
import { filterFieldsTransformer, filterFramesTransformer } from './transformers/filter';
|
import { filterFieldsTransformer, filterFramesTransformer } from './transformers/filter';
|
||||||
import { filterFieldsByNameTransformer, FilterFieldsByNameTransformerOptions } from './transformers/filterByName';
|
import { filterFieldsByNameTransformer } from './transformers/filterByName';
|
||||||
import { noopTransformer } from './transformers/noop';
|
import { noopTransformer } from './transformers/noop';
|
||||||
import { DataTransformerConfig, DataTransformerInfo } from '../types/transformations';
|
|
||||||
import { filterFramesByRefIdTransformer } from './transformers/filterByRefId';
|
import { filterFramesByRefIdTransformer } from './transformers/filterByRefId';
|
||||||
import { orderFieldsTransformer } from './transformers/order';
|
import { orderFieldsTransformer } from './transformers/order';
|
||||||
import { organizeFieldsTransformer } from './transformers/organize';
|
import { organizeFieldsTransformer } from './transformers/organize';
|
||||||
import { seriesToColumnsTransformer } from './transformers/seriesToColumns';
|
import { seriesToColumnsTransformer } from './transformers/seriesToColumns';
|
||||||
import { renameFieldsTransformer } from './transformers/rename';
|
import { renameFieldsTransformer } from './transformers/rename';
|
||||||
|
|
||||||
// Initalize the Registry
|
export const standardTransformers = {
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply configured transformations to the input data
|
|
||||||
*/
|
|
||||||
export function transformDataFrame(options: DataTransformerConfig[], data: DataFrame[]): DataFrame[] {
|
|
||||||
let processed = data;
|
|
||||||
for (const config of options) {
|
|
||||||
const info = transformersRegistry.get(config.id);
|
|
||||||
const transformer = info.transformer(config.options);
|
|
||||||
const after = transformer(processed);
|
|
||||||
|
|
||||||
// Add a key to the metadata if the data changed
|
|
||||||
if (after && after !== processed) {
|
|
||||||
for (const series of after) {
|
|
||||||
if (!series.meta) {
|
|
||||||
series.meta = {};
|
|
||||||
}
|
|
||||||
if (!series.meta.transformations) {
|
|
||||||
series.meta.transformations = [info.id];
|
|
||||||
} else {
|
|
||||||
series.meta.transformations = [...series.meta.transformations, info.id];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
processed = after;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return processed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registry of transformation options that can be driven by
|
|
||||||
* stored configuration files.
|
|
||||||
*/
|
|
||||||
class TransformerRegistry extends Registry<DataTransformerInfo> {
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
// Nacent options for more functional programming
|
|
||||||
// The API to these functions should change to match the actual
|
|
||||||
// needs of people trying to use it.
|
|
||||||
// filterFields|Frames is left off since it is likely easier to
|
|
||||||
// support with `frames.filter( f => {...} )`
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
|
|
||||||
append(data: DataFrame[], options?: AppendOptions): DataFrame | undefined {
|
|
||||||
return appendTransformer.transformer(options || appendTransformer.defaultOptions)(data)[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
reduce(data: DataFrame[], options: ReduceTransformerOptions): DataFrame[] {
|
|
||||||
return reduceTransformer.transformer(options)(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const transformersRegistry = new TransformerRegistry(() => [
|
|
||||||
noopTransformer,
|
noopTransformer,
|
||||||
filterFieldsTransformer,
|
filterFieldsTransformer,
|
||||||
filterFieldsByNameTransformer,
|
filterFieldsByNameTransformer,
|
||||||
@ -76,6 +21,4 @@ export const transformersRegistry = new TransformerRegistry(() => [
|
|||||||
reduceTransformer,
|
reduceTransformer,
|
||||||
seriesToColumnsTransformer,
|
seriesToColumnsTransformer,
|
||||||
renameFieldsTransformer,
|
renameFieldsTransformer,
|
||||||
]);
|
};
|
||||||
|
|
||||||
export { ReduceTransformerOptions, FilterFieldsByNameTransformerOptions };
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { DataTransformerID } from './ids';
|
import { DataTransformerID } from './ids';
|
||||||
import { toDataFrame } from '../../dataframe/processDataFrame';
|
import { toDataFrame } from '../../dataframe/processDataFrame';
|
||||||
import { transformDataFrame } from '../transformers';
|
import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry';
|
||||||
import { transformersRegistry } from '../transformers';
|
import { appendTransformer } from './append';
|
||||||
|
import { transformDataFrame } from '../transformDataFrame';
|
||||||
|
|
||||||
const seriesAB = toDataFrame({
|
const seriesAB = toDataFrame({
|
||||||
columns: [{ text: 'A' }, { text: 'B' }],
|
columns: [{ text: 'A' }, { text: 'B' }],
|
||||||
@ -20,13 +21,14 @@ const seriesBC = toDataFrame({
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Append Transformer', () => {
|
describe('Append Transformer', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
mockTransformationsRegistry([appendTransformer]);
|
||||||
|
});
|
||||||
it('filters by include', () => {
|
it('filters by include', () => {
|
||||||
const cfg = {
|
const cfg = {
|
||||||
id: DataTransformerID.append,
|
id: DataTransformerID.append,
|
||||||
options: {},
|
options: {},
|
||||||
};
|
};
|
||||||
const x = transformersRegistry.get(DataTransformerID.append);
|
|
||||||
expect(x.id).toBe(cfg.id);
|
|
||||||
|
|
||||||
const processed = transformDataFrame([cfg], [seriesAB, seriesBC])[0];
|
const processed = transformDataFrame([cfg], [seriesAB, seriesBC])[0];
|
||||||
expect(processed.fields.length).toBe(3);
|
expect(processed.fields.length).toBe(3);
|
||||||
|
@ -2,7 +2,9 @@ import { FieldType } from '../../types/dataFrame';
|
|||||||
import { DataTransformerID } from './ids';
|
import { DataTransformerID } from './ids';
|
||||||
import { toDataFrame } from '../../dataframe/processDataFrame';
|
import { toDataFrame } from '../../dataframe/processDataFrame';
|
||||||
import { FieldMatcherID } from '../matchers/ids';
|
import { FieldMatcherID } from '../matchers/ids';
|
||||||
import { transformDataFrame } from '../transformers';
|
import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry';
|
||||||
|
import { filterFieldsTransformer } from './filter';
|
||||||
|
import { transformDataFrame } from '../transformDataFrame';
|
||||||
|
|
||||||
export const simpleSeriesWithTypes = toDataFrame({
|
export const simpleSeriesWithTypes = toDataFrame({
|
||||||
fields: [
|
fields: [
|
||||||
@ -14,6 +16,10 @@ export const simpleSeriesWithTypes = toDataFrame({
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Filter Transformer', () => {
|
describe('Filter Transformer', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
mockTransformationsRegistry([filterFieldsTransformer]);
|
||||||
|
});
|
||||||
|
|
||||||
it('filters by include', () => {
|
it('filters by include', () => {
|
||||||
const cfg = {
|
const cfg = {
|
||||||
id: DataTransformerID.filterFields,
|
id: DataTransformerID.filterFields,
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import { DataTransformerID } from './ids';
|
import { DataTransformerID } from './ids';
|
||||||
import { transformDataFrame } from '../transformers';
|
|
||||||
import { toDataFrame } from '../../dataframe/processDataFrame';
|
import { toDataFrame } from '../../dataframe/processDataFrame';
|
||||||
import { FieldType } from '../../types/dataFrame';
|
import { FieldType } from '../../types/dataFrame';
|
||||||
|
import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry';
|
||||||
|
import { filterFieldsByNameTransformer } from './filterByName';
|
||||||
|
import { filterFieldsTransformer } from './filter';
|
||||||
|
import { transformDataFrame } from '../transformDataFrame';
|
||||||
|
|
||||||
export const seriesWithNamesToMatch = toDataFrame({
|
export const seriesWithNamesToMatch = toDataFrame({
|
||||||
fields: [
|
fields: [
|
||||||
@ -13,6 +16,10 @@ export const seriesWithNamesToMatch = toDataFrame({
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('filterByName transformer', () => {
|
describe('filterByName transformer', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
mockTransformationsRegistry([filterFieldsByNameTransformer, filterFieldsTransformer]);
|
||||||
|
});
|
||||||
|
|
||||||
it('returns original series if no options provided', () => {
|
it('returns original series if no options provided', () => {
|
||||||
const cfg = {
|
const cfg = {
|
||||||
id: DataTransformerID.filterFields,
|
id: DataTransformerID.filterFields,
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { DataTransformerID } from './ids';
|
import { DataTransformerID } from './ids';
|
||||||
import { transformDataFrame } from '../transformers';
|
|
||||||
import { toDataFrame } from '../../dataframe/processDataFrame';
|
import { toDataFrame } from '../../dataframe/processDataFrame';
|
||||||
|
import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry';
|
||||||
|
import { filterFramesByRefIdTransformer } from './filterByRefId';
|
||||||
|
import { transformDataFrame } from '../transformDataFrame';
|
||||||
|
|
||||||
export const allSeries = [
|
export const allSeries = [
|
||||||
toDataFrame({
|
toDataFrame({
|
||||||
@ -18,6 +20,9 @@ export const allSeries = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
describe('filterByRefId transformer', () => {
|
describe('filterByRefId transformer', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
mockTransformationsRegistry([filterFramesByRefIdTransformer]);
|
||||||
|
});
|
||||||
it('returns all series if no options provided', () => {
|
it('returns all series if no options provided', () => {
|
||||||
const cfg = {
|
const cfg = {
|
||||||
id: DataTransformerID.filterByRefId,
|
id: DataTransformerID.filterByRefId,
|
||||||
|
@ -6,9 +6,13 @@ import {
|
|||||||
toDataFrame,
|
toDataFrame,
|
||||||
transformDataFrame,
|
transformDataFrame,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { OrderFieldsTransformerOptions } from './order';
|
import { orderFieldsTransformer, OrderFieldsTransformerOptions } from './order';
|
||||||
|
import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry';
|
||||||
|
|
||||||
describe('Order Transformer', () => {
|
describe('Order Transformer', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
mockTransformationsRegistry([orderFieldsTransformer]);
|
||||||
|
});
|
||||||
describe('when consistent data is received', () => {
|
describe('when consistent data is received', () => {
|
||||||
const data = toDataFrame({
|
const data = toDataFrame({
|
||||||
name: 'A',
|
name: 'A',
|
||||||
|
@ -6,9 +6,14 @@ import {
|
|||||||
toDataFrame,
|
toDataFrame,
|
||||||
transformDataFrame,
|
transformDataFrame,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { OrganizeFieldsTransformerOptions } from './organize';
|
import { organizeFieldsTransformer, OrganizeFieldsTransformerOptions } from './organize';
|
||||||
|
import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry';
|
||||||
|
|
||||||
describe('OrganizeFields Transformer', () => {
|
describe('OrganizeFields Transformer', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
mockTransformationsRegistry([organizeFieldsTransformer]);
|
||||||
|
});
|
||||||
|
|
||||||
describe('when consistent data is received', () => {
|
describe('when consistent data is received', () => {
|
||||||
const data = toDataFrame({
|
const data = toDataFrame({
|
||||||
name: 'A',
|
name: 'A',
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { ReducerID } from '../fieldReducer';
|
import { ReducerID } from '../fieldReducer';
|
||||||
import { DataTransformerID } from './ids';
|
import { DataTransformerID } from './ids';
|
||||||
import { toDataFrame, toDataFrameDTO } from '../../dataframe/processDataFrame';
|
import { toDataFrame, toDataFrameDTO } from '../../dataframe/processDataFrame';
|
||||||
import { transformDataFrame } from '../transformers';
|
import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry';
|
||||||
|
import { reduceTransformer } from './reduce';
|
||||||
|
import { transformDataFrame } from '../transformDataFrame';
|
||||||
|
|
||||||
const seriesWithValues = toDataFrame({
|
const seriesWithValues = toDataFrame({
|
||||||
fields: [
|
fields: [
|
||||||
@ -11,6 +13,9 @@ const seriesWithValues = toDataFrame({
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Reducer Transformer', () => {
|
describe('Reducer Transformer', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
mockTransformationsRegistry([reduceTransformer]);
|
||||||
|
});
|
||||||
it('filters by include', () => {
|
it('filters by include', () => {
|
||||||
const cfg = {
|
const cfg = {
|
||||||
id: DataTransformerID.reduce,
|
id: DataTransformerID.reduce,
|
||||||
|
@ -6,9 +6,14 @@ import {
|
|||||||
toDataFrame,
|
toDataFrame,
|
||||||
transformDataFrame,
|
transformDataFrame,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { RenameFieldsTransformerOptions } from './rename';
|
import { RenameFieldsTransformerOptions, renameFieldsTransformer } from './rename';
|
||||||
|
import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry';
|
||||||
|
|
||||||
describe('Rename Transformer', () => {
|
describe('Rename Transformer', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
mockTransformationsRegistry([renameFieldsTransformer]);
|
||||||
|
});
|
||||||
|
|
||||||
describe('when consistent data is received', () => {
|
describe('when consistent data is received', () => {
|
||||||
const data = toDataFrame({
|
const data = toDataFrame({
|
||||||
name: 'A',
|
name: 'A',
|
||||||
|
@ -6,9 +6,13 @@ import {
|
|||||||
toDataFrame,
|
toDataFrame,
|
||||||
transformDataFrame,
|
transformDataFrame,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { SeriesToColumnsOptions } from './seriesToColumns';
|
import { SeriesToColumnsOptions, seriesToColumnsTransformer } from './seriesToColumns';
|
||||||
|
import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry';
|
||||||
|
|
||||||
describe('SeriesToColumns Transformer', () => {
|
describe('SeriesToColumns Transformer', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
mockTransformationsRegistry([seriesToColumnsTransformer]);
|
||||||
|
});
|
||||||
const everySecondSeries = toDataFrame({
|
const everySecondSeries = toDataFrame({
|
||||||
name: 'even',
|
name: 'even',
|
||||||
fields: [
|
fields: [
|
||||||
|
@ -2,16 +2,26 @@ import { DataFrame, Field } from './dataFrame';
|
|||||||
import { RegistryItemWithOptions } from '../utils/Registry';
|
import { RegistryItemWithOptions } from '../utils/Registry';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Immutable data transformation
|
* Function that transform data frames (AKA transformer)
|
||||||
*/
|
*/
|
||||||
export type DataTransformer = (data: DataFrame[]) => DataFrame[];
|
export type DataTransformer = (data: DataFrame[]) => DataFrame[];
|
||||||
|
|
||||||
export interface DataTransformerInfo<TOptions = any> extends RegistryItemWithOptions {
|
export interface DataTransformerInfo<TOptions = any> extends RegistryItemWithOptions {
|
||||||
|
/**
|
||||||
|
* Function that configures transformation and returns a transformer
|
||||||
|
* @param options
|
||||||
|
*/
|
||||||
transformer: (options: TOptions) => DataTransformer;
|
transformer: (options: TOptions) => DataTransformer;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DataTransformerConfig<TOptions = any> {
|
export interface DataTransformerConfig<TOptions = any> {
|
||||||
|
/**
|
||||||
|
* Unique identifier of transformer
|
||||||
|
*/
|
||||||
id: string;
|
id: string;
|
||||||
|
/**
|
||||||
|
* Options to be passed to the transformer
|
||||||
|
*/
|
||||||
options: TOptions;
|
options: TOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
import { standardTransformersRegistry } from '../../transformations';
|
||||||
|
import { DataTransformerInfo } from '../../types';
|
||||||
|
|
||||||
|
export const mockTransformationsRegistry = (transformers: Array<DataTransformerInfo<any>>) => {
|
||||||
|
standardTransformersRegistry.setInit(() => {
|
||||||
|
return transformers.map(t => {
|
||||||
|
return {
|
||||||
|
id: t.id,
|
||||||
|
name: t.name,
|
||||||
|
transformation: t,
|
||||||
|
description: t.description,
|
||||||
|
editor: () => null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
@ -25,15 +25,13 @@ const buttonVariantStyles = (from: string, to: string, textColor: string) => css
|
|||||||
const getPropertiesForVariant = (theme: GrafanaTheme, variant: ButtonVariant) => {
|
const getPropertiesForVariant = (theme: GrafanaTheme, variant: ButtonVariant) => {
|
||||||
switch (variant) {
|
switch (variant) {
|
||||||
case 'secondary':
|
case 'secondary':
|
||||||
const from = selectThemeVariant({ light: theme.colors.gray7, dark: theme.colors.gray15 }, theme.type) as string;
|
const from = selectThemeVariant({ light: theme.colors.gray7, dark: theme.colors.gray10 }, theme.type) as string;
|
||||||
const to = selectThemeVariant(
|
const to = selectThemeVariant(
|
||||||
{
|
{
|
||||||
light: tinycolor(from)
|
light: tinycolor(from)
|
||||||
.darken(5)
|
.darken(5)
|
||||||
.toString(),
|
.toString(),
|
||||||
dark: tinycolor(from)
|
dark: theme.colors.gray05,
|
||||||
.lighten(4)
|
|
||||||
.toString(),
|
|
||||||
},
|
},
|
||||||
theme.type
|
theme.type
|
||||||
) as string;
|
) as string;
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { FilterFieldsByNameTransformerOptions, DataTransformerID, transformersRegistry, KeyValue } from '@grafana/data';
|
import {
|
||||||
import { TransformerUIProps, TransformerUIRegistyItem } from './types';
|
DataTransformerID,
|
||||||
|
FilterFieldsByNameTransformerOptions,
|
||||||
|
KeyValue,
|
||||||
|
standardTransformers,
|
||||||
|
TransformerRegistyItem,
|
||||||
|
TransformerUIProps,
|
||||||
|
} from '@grafana/data';
|
||||||
import { ThemeContext } from '../../themes/ThemeContext';
|
import { ThemeContext } from '../../themes/ThemeContext';
|
||||||
import { css, cx } from 'emotion';
|
import { css, cx } from 'emotion';
|
||||||
import { InlineList } from '../List/InlineList';
|
import { InlineList } from '../List/InlineList';
|
||||||
@ -154,10 +160,10 @@ const FilterPill: React.FC<FilterPillProps> = ({ label, selected, onClick }) =>
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const filterFieldsByNameTransformRegistryItem: TransformerUIRegistyItem<FilterFieldsByNameTransformerOptions> = {
|
export const filterFieldsByNameTransformRegistryItem: TransformerRegistyItem<FilterFieldsByNameTransformerOptions> = {
|
||||||
id: DataTransformerID.filterFieldsByName,
|
id: DataTransformerID.filterFieldsByName,
|
||||||
component: FilterByNameTransformerEditor,
|
editor: FilterByNameTransformerEditor,
|
||||||
transformer: transformersRegistry.get(DataTransformerID.filterFieldsByName),
|
transformation: standardTransformers.filterFieldsByNameTransformer,
|
||||||
name: 'Filter by name',
|
name: 'Filter by name',
|
||||||
description: 'UI for filter by name transformation',
|
description: 'Filter fields by name',
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import {
|
import {
|
||||||
FilterFramesByRefIdTransformerOptions,
|
|
||||||
DataTransformerID,
|
DataTransformerID,
|
||||||
transformersRegistry,
|
FilterFramesByRefIdTransformerOptions,
|
||||||
KeyValue,
|
KeyValue,
|
||||||
|
standardTransformers,
|
||||||
|
TransformerRegistyItem,
|
||||||
|
TransformerUIProps,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { TransformerUIProps, TransformerUIRegistyItem } from './types';
|
|
||||||
import { ThemeContext } from '../../themes/ThemeContext';
|
import { ThemeContext } from '../../themes/ThemeContext';
|
||||||
import { css, cx } from 'emotion';
|
import { css, cx } from 'emotion';
|
||||||
import { InlineList } from '../List/InlineList';
|
import { InlineList } from '../List/InlineList';
|
||||||
@ -159,10 +160,10 @@ const FilterPill: React.FC<FilterPillProps> = ({ label, selected, onClick }) =>
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const filterFramesByRefIdTransformRegistryItem: TransformerUIRegistyItem<FilterFramesByRefIdTransformerOptions> = {
|
export const filterFramesByRefIdTransformRegistryItem: TransformerRegistyItem<FilterFramesByRefIdTransformerOptions> = {
|
||||||
id: DataTransformerID.filterByRefId,
|
id: DataTransformerID.filterByRefId,
|
||||||
component: FilterByRefIdTransformerEditor,
|
editor: FilterByRefIdTransformerEditor,
|
||||||
transformer: transformersRegistry.get(DataTransformerID.filterByRefId),
|
transformation: standardTransformers.filterFramesByRefIdTransformer,
|
||||||
name: 'Filter by refId',
|
name: 'Filter by refId',
|
||||||
description: 'Filter results by refId',
|
description: 'Filter results by refId',
|
||||||
};
|
};
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import React, { useMemo, useCallback } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import { css, cx } from 'emotion';
|
import { css, cx } from 'emotion';
|
||||||
import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd';
|
import { DragDropContext, Draggable, Droppable, DropResult } from 'react-beautiful-dnd';
|
||||||
import {
|
import {
|
||||||
DataTransformerID,
|
|
||||||
transformersRegistry,
|
|
||||||
DataFrame,
|
|
||||||
GrafanaTheme,
|
|
||||||
createOrderFieldsComparer,
|
createOrderFieldsComparer,
|
||||||
|
DataFrame,
|
||||||
|
DataTransformerID,
|
||||||
|
GrafanaTheme,
|
||||||
OrganizeFieldsTransformerOptions,
|
OrganizeFieldsTransformerOptions,
|
||||||
|
standardTransformers,
|
||||||
|
TransformerRegistyItem,
|
||||||
|
TransformerUIProps,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { TransformerUIRegistyItem, TransformerUIProps } from './types';
|
|
||||||
import { stylesFactory, useTheme } from '../../themes';
|
import { stylesFactory, useTheme } from '../../themes';
|
||||||
import { Button } from '../Button/Button';
|
import { Button } from '../Button/Button';
|
||||||
import { VerticalGroup } from '../Layout/Layout';
|
import { VerticalGroup } from '../Layout/Layout';
|
||||||
@ -217,10 +218,10 @@ const fieldNamesFromInput = (input: DataFrame[]): string[] => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const organizeFieldsTransformRegistryItem: TransformerUIRegistyItem<OrganizeFieldsTransformerOptions> = {
|
export const organizeFieldsTransformRegistryItem: TransformerRegistyItem<OrganizeFieldsTransformerOptions> = {
|
||||||
id: DataTransformerID.organize,
|
id: DataTransformerID.organize,
|
||||||
component: OrganizeFieldsTransformerEditor,
|
editor: OrganizeFieldsTransformerEditor,
|
||||||
transformer: transformersRegistry.get(DataTransformerID.organize),
|
transformation: standardTransformers.organizeFieldsTransformer,
|
||||||
name: 'Organize fields',
|
name: 'Organize fields',
|
||||||
description: 'UI for organizing fields',
|
description: 'Order, filter and rename fields',
|
||||||
};
|
};
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StatsPicker } from '../StatsPicker/StatsPicker';
|
import { StatsPicker } from '../StatsPicker/StatsPicker';
|
||||||
import { ReduceTransformerOptions, DataTransformerID, ReducerID, transformersRegistry } from '@grafana/data';
|
import {
|
||||||
import { TransformerUIRegistyItem, TransformerUIProps } from './types';
|
ReduceTransformerOptions,
|
||||||
|
DataTransformerID,
|
||||||
|
ReducerID,
|
||||||
|
standardTransformers,
|
||||||
|
TransformerRegistyItem,
|
||||||
|
TransformerUIProps,
|
||||||
|
} from '@grafana/data';
|
||||||
|
|
||||||
// TODO: Minimal implementation, needs some <3
|
// TODO: Minimal implementation, needs some <3
|
||||||
export const ReduceTransformerEditor: React.FC<TransformerUIProps<ReduceTransformerOptions>> = ({
|
export const ReduceTransformerEditor: React.FC<TransformerUIProps<ReduceTransformerOptions>> = ({
|
||||||
options,
|
options,
|
||||||
onChange,
|
onChange,
|
||||||
input,
|
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<StatsPicker
|
<StatsPicker
|
||||||
@ -25,10 +30,10 @@ export const ReduceTransformerEditor: React.FC<TransformerUIProps<ReduceTransfor
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const reduceTransformRegistryItem: TransformerUIRegistyItem<ReduceTransformerOptions> = {
|
export const reduceTransformRegistryItem: TransformerRegistyItem<ReduceTransformerOptions> = {
|
||||||
id: DataTransformerID.reduce,
|
id: DataTransformerID.reduce,
|
||||||
component: ReduceTransformerEditor,
|
editor: ReduceTransformerEditor,
|
||||||
transformer: transformersRegistry.get(DataTransformerID.reduce),
|
transformation: standardTransformers.reduceTransformer,
|
||||||
name: 'Reduce',
|
name: 'Reduce',
|
||||||
description: 'UI for reduce transformation',
|
description: 'Return a DataFrame with the reduction results',
|
||||||
};
|
};
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
import { Registry } from '@grafana/data';
|
|
||||||
import { reduceTransformRegistryItem } from './ReduceTransformerEditor';
|
|
||||||
import { filterFieldsByNameTransformRegistryItem } from './FilterByNameTransformerEditor';
|
|
||||||
import { filterFramesByRefIdTransformRegistryItem } from './FilterByRefIdTransformerEditor';
|
|
||||||
import { TransformerUIRegistyItem } from './types';
|
|
||||||
import { organizeFieldsTransformRegistryItem } from './OrganizeFieldsTransformerEditor';
|
|
||||||
|
|
||||||
export const transformersUIRegistry = new Registry<TransformerUIRegistyItem<any>>(() => {
|
|
||||||
return [
|
|
||||||
reduceTransformRegistryItem,
|
|
||||||
filterFieldsByNameTransformRegistryItem,
|
|
||||||
filterFramesByRefIdTransformRegistryItem,
|
|
||||||
organizeFieldsTransformRegistryItem,
|
|
||||||
];
|
|
||||||
});
|
|
@ -1,15 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { DataFrame, RegistryItem, DataTransformerInfo } from '@grafana/data';
|
|
||||||
|
|
||||||
export interface TransformerUIRegistyItem<TOptions> extends RegistryItem {
|
|
||||||
component: React.ComponentType<TransformerUIProps<TOptions>>;
|
|
||||||
transformer: DataTransformerInfo<TOptions>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TransformerUIProps<T> {
|
|
||||||
// Transformer configuration, persisted on panel's model
|
|
||||||
options: T;
|
|
||||||
// Pre-transformation data frame
|
|
||||||
input: DataFrame[];
|
|
||||||
onChange: (options: T) => void;
|
|
||||||
}
|
|
@ -4,6 +4,7 @@ import { SelectableValue } from '@grafana/data';
|
|||||||
import { Button, ButtonVariant } from '../Button';
|
import { Button, ButtonVariant } from '../Button';
|
||||||
import { Select } from '../Select/Select';
|
import { Select } from '../Select/Select';
|
||||||
import { FullWidthButtonContainer } from '../Button/FullWidthButtonContainer';
|
import { FullWidthButtonContainer } from '../Button/FullWidthButtonContainer';
|
||||||
|
import { ComponentSize } from '../../types/size';
|
||||||
|
|
||||||
interface ValuePickerProps<T> {
|
interface ValuePickerProps<T> {
|
||||||
/** Label to display on the picker button */
|
/** Label to display on the picker button */
|
||||||
@ -14,20 +15,29 @@ interface ValuePickerProps<T> {
|
|||||||
options: Array<SelectableValue<T>>;
|
options: Array<SelectableValue<T>>;
|
||||||
onChange: (value: SelectableValue<T>) => void;
|
onChange: (value: SelectableValue<T>) => void;
|
||||||
variant?: ButtonVariant;
|
variant?: ButtonVariant;
|
||||||
|
size?: ComponentSize;
|
||||||
|
isFullWidth?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ValuePicker<T>({ label, icon, options, onChange, variant }: ValuePickerProps<T>) {
|
export function ValuePicker<T>({
|
||||||
|
label,
|
||||||
|
icon,
|
||||||
|
options,
|
||||||
|
onChange,
|
||||||
|
variant,
|
||||||
|
size,
|
||||||
|
isFullWidth = true,
|
||||||
|
}: ValuePickerProps<T>) {
|
||||||
const [isPicking, setIsPicking] = useState(false);
|
const [isPicking, setIsPicking] = useState(false);
|
||||||
|
|
||||||
|
const buttonEl = (
|
||||||
|
<Button size={size || 'sm'} icon={icon || 'plus-circle'} onClick={() => setIsPicking(true)} variant={variant}>
|
||||||
|
{label}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!isPicking && (
|
{!isPicking && (isFullWidth ? <FullWidthButtonContainer>{buttonEl}</FullWidthButtonContainer> : buttonEl)}
|
||||||
<FullWidthButtonContainer>
|
|
||||||
<Button size="sm" icon={icon || 'plus-circle'} onClick={() => setIsPicking(true)} variant={variant}>
|
|
||||||
{label}
|
|
||||||
</Button>
|
|
||||||
</FullWidthButtonContainer>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isPicking && (
|
{isPicking && (
|
||||||
<Select
|
<Select
|
||||||
|
@ -100,7 +100,7 @@ export { DataLinksInlineEditor } from './DataLinks/DataLinksInlineEditor/DataLin
|
|||||||
export { DataLinkInput } from './DataLinks/DataLinkInput';
|
export { DataLinkInput } from './DataLinks/DataLinkInput';
|
||||||
export { DataLinksContextMenu } from './DataLinks/DataLinksContextMenu';
|
export { DataLinksContextMenu } from './DataLinks/DataLinksContextMenu';
|
||||||
export { SeriesIcon } from './Legend/SeriesIcon';
|
export { SeriesIcon } from './Legend/SeriesIcon';
|
||||||
export { transformersUIRegistry } from './TransformersUI/transformers';
|
|
||||||
export { JSONFormatter } from './JSONFormatter/JSONFormatter';
|
export { JSONFormatter } from './JSONFormatter/JSONFormatter';
|
||||||
export { JsonExplorer } from './JSONFormatter/json_explorer/json_explorer';
|
export { JsonExplorer } from './JSONFormatter/json_explorer/json_explorer';
|
||||||
export { ErrorBoundary, ErrorBoundaryAlert } from './ErrorBoundary/ErrorBoundary';
|
export { ErrorBoundary, ErrorBoundaryAlert } from './ErrorBoundary/ErrorBoundary';
|
||||||
|
@ -8,8 +8,9 @@ export { default as ansicolor } from './ansicolor';
|
|||||||
|
|
||||||
import * as DOMUtil from './dom'; // includes Element.closest polyfil
|
import * as DOMUtil from './dom'; // includes Element.closest polyfil
|
||||||
export { DOMUtil };
|
export { DOMUtil };
|
||||||
|
export { renderOrCallToRender } from './renderOrCallToRender';
|
||||||
|
|
||||||
// Exposes standard editors for registries of optionsUi config and panel options UI
|
// Exposes standard editors for registries of optionsUi config and panel options UI
|
||||||
export { getStandardFieldConfigs, getStandardOptionEditors } from './standardEditors';
|
export { getStandardFieldConfigs, getStandardOptionEditors } from './standardEditors';
|
||||||
|
// Exposes standard transformers for registry of Transformations
|
||||||
export { renderOrCallToRender } from './renderOrCallToRender';
|
export { getStandardTransformers } from './standardTransformers';
|
||||||
|
14
packages/grafana-ui/src/utils/standardTransformers.ts
Normal file
14
packages/grafana-ui/src/utils/standardTransformers.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { TransformerRegistyItem } from '@grafana/data';
|
||||||
|
import { reduceTransformRegistryItem } from '../components/TransformersUI/ReduceTransformerEditor';
|
||||||
|
import { filterFieldsByNameTransformRegistryItem } from '../components/TransformersUI/FilterByNameTransformerEditor';
|
||||||
|
import { filterFramesByRefIdTransformRegistryItem } from '../components/TransformersUI/FilterByRefIdTransformerEditor';
|
||||||
|
import { organizeFieldsTransformRegistryItem } from '../components/TransformersUI/OrganizeFieldsTransformerEditor';
|
||||||
|
|
||||||
|
export const getStandardTransformers = (): Array<TransformerRegistyItem<any>> => {
|
||||||
|
return [
|
||||||
|
reduceTransformRegistryItem,
|
||||||
|
filterFieldsByNameTransformRegistryItem,
|
||||||
|
filterFramesByRefIdTransformRegistryItem,
|
||||||
|
organizeFieldsTransformRegistryItem,
|
||||||
|
];
|
||||||
|
};
|
@ -31,6 +31,7 @@ import {
|
|||||||
setMarkdownOptions,
|
setMarkdownOptions,
|
||||||
standardEditorsRegistry,
|
standardEditorsRegistry,
|
||||||
standardFieldConfigEditorRegistry,
|
standardFieldConfigEditorRegistry,
|
||||||
|
standardTransformersRegistry,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
import { addClassIfNoOverlayScrollbar } from 'app/core/utils/scrollbar';
|
import { addClassIfNoOverlayScrollbar } from 'app/core/utils/scrollbar';
|
||||||
@ -45,7 +46,7 @@ import { reportPerformance } from './core/services/echo/EchoSrv';
|
|||||||
import { PerformanceBackend } from './core/services/echo/backends/PerformanceBackend';
|
import { PerformanceBackend } from './core/services/echo/backends/PerformanceBackend';
|
||||||
import 'app/routes/GrafanaCtrl';
|
import 'app/routes/GrafanaCtrl';
|
||||||
import 'app/features/all';
|
import 'app/features/all';
|
||||||
import { getStandardFieldConfigs, getStandardOptionEditors } from '@grafana/ui';
|
import { getStandardFieldConfigs, getStandardOptionEditors, getStandardTransformers } from '@grafana/ui';
|
||||||
import { getDefaultVariableAdapters, variableAdapters } from './features/variables/adapters';
|
import { getDefaultVariableAdapters, variableAdapters } from './features/variables/adapters';
|
||||||
import { initDevFeatures } from './dev';
|
import { initDevFeatures } from './dev';
|
||||||
|
|
||||||
@ -97,6 +98,7 @@ export class GrafanaApp {
|
|||||||
|
|
||||||
standardEditorsRegistry.setInit(getStandardOptionEditors);
|
standardEditorsRegistry.setInit(getStandardOptionEditors);
|
||||||
standardFieldConfigEditorRegistry.setInit(getStandardFieldConfigs);
|
standardFieldConfigEditorRegistry.setInit(getStandardFieldConfigs);
|
||||||
|
standardTransformersRegistry.setInit(getStandardTransformers);
|
||||||
variableAdapters.setInit(getDefaultVariableAdapters);
|
variableAdapters.setInit(getDefaultVariableAdapters);
|
||||||
|
|
||||||
app.config(
|
app.config(
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
import { css } from 'emotion';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { transformersUIRegistry } from '@grafana/ui';
|
import { Container, CustomScrollbar, ValuePicker } from '@grafana/ui';
|
||||||
import { DataTransformerConfig, DataFrame, transformDataFrame, SelectableValue } from '@grafana/data';
|
import {
|
||||||
import { Button, CustomScrollbar, Select, Container } from '@grafana/ui';
|
DataFrame,
|
||||||
|
DataTransformerConfig,
|
||||||
|
SelectableValue,
|
||||||
|
standardTransformersRegistry,
|
||||||
|
transformDataFrame,
|
||||||
|
} from '@grafana/data';
|
||||||
import { TransformationOperationRow } from './TransformationOperationRow';
|
import { TransformationOperationRow } from './TransformationOperationRow';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -11,13 +15,7 @@ interface Props {
|
|||||||
dataFrames: DataFrame[];
|
dataFrames: DataFrame[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
export class TransformationsEditor extends React.PureComponent<Props> {
|
||||||
addingTransformation: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TransformationsEditor extends React.PureComponent<Props, State> {
|
|
||||||
state = { addingTransformation: false };
|
|
||||||
|
|
||||||
onTransformationAdd = (selectable: SelectableValue<string>) => {
|
onTransformationAdd = (selectable: SelectableValue<string>) => {
|
||||||
const { transformations, onChange } = this.props;
|
const { transformations, onChange } = this.props;
|
||||||
onChange([
|
onChange([
|
||||||
@ -27,7 +25,6 @@ export class TransformationsEditor extends React.PureComponent<Props, State> {
|
|||||||
options: {},
|
options: {},
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
this.setState({ addingTransformation: false });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onTransformationChange = (idx: number, config: DataTransformerConfig) => {
|
onTransformationChange = (idx: number, config: DataTransformerConfig) => {
|
||||||
@ -45,32 +42,23 @@ export class TransformationsEditor extends React.PureComponent<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
renderTransformationSelector = () => {
|
renderTransformationSelector = () => {
|
||||||
if (!this.state.addingTransformation) {
|
const availableTransformers = standardTransformersRegistry.list().map(t => {
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const availableTransformers = transformersUIRegistry.list().map(t => {
|
|
||||||
return {
|
return {
|
||||||
value: t.transformer.id,
|
value: t.transformation.id,
|
||||||
label: t.transformer.name,
|
label: t.name,
|
||||||
|
description: t.description,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<ValuePicker
|
||||||
className={css`
|
size="md"
|
||||||
margin-bottom: 10px;
|
variant="secondary"
|
||||||
max-width: 300px;
|
label="Add transformation"
|
||||||
`}
|
options={availableTransformers}
|
||||||
>
|
onChange={this.onTransformationAdd}
|
||||||
<Select
|
isFullWidth={false}
|
||||||
options={availableTransformers}
|
/>
|
||||||
placeholder="Select transformation"
|
|
||||||
onChange={this.onTransformationAdd}
|
|
||||||
autoFocus={true}
|
|
||||||
openMenuOnFocus={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -83,7 +71,7 @@ export class TransformationsEditor extends React.PureComponent<Props, State> {
|
|||||||
{transformations.map((t, i) => {
|
{transformations.map((t, i) => {
|
||||||
let editor;
|
let editor;
|
||||||
|
|
||||||
const transformationUI = transformersUIRegistry.getIfExists(t.id);
|
const transformationUI = standardTransformersRegistry.getIfExists(t.id);
|
||||||
if (!transformationUI) {
|
if (!transformationUI) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -92,8 +80,8 @@ export class TransformationsEditor extends React.PureComponent<Props, State> {
|
|||||||
const output = transformDataFrame(transformations.slice(i), input);
|
const output = transformDataFrame(transformations.slice(i), input);
|
||||||
|
|
||||||
if (transformationUI) {
|
if (transformationUI) {
|
||||||
editor = React.createElement(transformationUI.component, {
|
editor = React.createElement(transformationUI.editor, {
|
||||||
options: { ...transformationUI.transformer.defaultOptions, ...t.options },
|
options: { ...transformationUI.transformation.defaultOptions, ...t.options },
|
||||||
input,
|
input,
|
||||||
onChange: (options: any) => {
|
onChange: (options: any) => {
|
||||||
this.onTransformationChange(i, {
|
this.onTransformationChange(i, {
|
||||||
@ -130,9 +118,6 @@ export class TransformationsEditor extends React.PureComponent<Props, State> {
|
|||||||
</p>
|
</p>
|
||||||
{this.renderTransformationEditors()}
|
{this.renderTransformationEditors()}
|
||||||
{this.renderTransformationSelector()}
|
{this.renderTransformationSelector()}
|
||||||
<Button variant="secondary" icon="plus" onClick={() => this.setState({ addingTransformation: true })}>
|
|
||||||
Add transformation
|
|
||||||
</Button>
|
|
||||||
</Container>
|
</Container>
|
||||||
</CustomScrollbar>
|
</CustomScrollbar>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user