Commit 58257a95 by Dominik Prokop Committed by GitHub

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
parent 7f61f3cc
export * from './matchers/ids';
export * from './transformers/ids';
export * from './matchers';
export * from './transformers';
export { standardTransformers } from './transformers';
export * from './fieldReducer';
export { FilterFieldsByNameTransformerOptions } from './transformers/filterByName';
export { FilterFramesByRefIdTransformerOptions } from './transformers/filterByRefId';
export { ReduceTransformerOptions } from './transformers/reduce';
export { OrganizeFieldsTransformerOptions } from './transformers/organize';
export { createOrderFieldsComparer } from './transformers/order';
export { transformDataFrame } from './transformDataFrame';
export {
TransformerRegistyItem,
TransformerUIProps,
standardTransformersRegistry,
} from './standardTransformersRegistry';
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>>();
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;
}
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
]);
});
});
import { DataFrame } from '../types/dataFrame';
import { Registry } from '../utils/Registry';
import { AppendOptions, appendTransformer } from './transformers/append';
import { reduceTransformer, ReduceTransformerOptions } from './transformers/reduce';
import { appendTransformer } from './transformers/append';
import { reduceTransformer } from './transformers/reduce';
import { filterFieldsTransformer, filterFramesTransformer } from './transformers/filter';
import { filterFieldsByNameTransformer, FilterFieldsByNameTransformerOptions } from './transformers/filterByName';
import { filterFieldsByNameTransformer } from './transformers/filterByName';
import { noopTransformer } from './transformers/noop';
import { DataTransformerConfig, DataTransformerInfo } from '../types/transformations';
import { filterFramesByRefIdTransformer } from './transformers/filterByRefId';
import { orderFieldsTransformer } from './transformers/order';
import { organizeFieldsTransformer } from './transformers/organize';
import { seriesToColumnsTransformer } from './transformers/seriesToColumns';
import { renameFieldsTransformer } from './transformers/rename';
// Initalize the Registry
/**
* 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(() => [
export const standardTransformers = {
noopTransformer,
filterFieldsTransformer,
filterFieldsByNameTransformer,
......@@ -76,6 +21,4 @@ export const transformersRegistry = new TransformerRegistry(() => [
reduceTransformer,
seriesToColumnsTransformer,
renameFieldsTransformer,
]);
export { ReduceTransformerOptions, FilterFieldsByNameTransformerOptions };
};
import { DataTransformerID } from './ids';
import { toDataFrame } from '../../dataframe/processDataFrame';
import { transformDataFrame } from '../transformers';
import { transformersRegistry } from '../transformers';
import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry';
import { appendTransformer } from './append';
import { transformDataFrame } from '../transformDataFrame';
const seriesAB = toDataFrame({
columns: [{ text: 'A' }, { text: 'B' }],
......@@ -20,13 +21,14 @@ const seriesBC = toDataFrame({
});
describe('Append Transformer', () => {
beforeAll(() => {
mockTransformationsRegistry([appendTransformer]);
});
it('filters by include', () => {
const cfg = {
id: DataTransformerID.append,
options: {},
};
const x = transformersRegistry.get(DataTransformerID.append);
expect(x.id).toBe(cfg.id);
const processed = transformDataFrame([cfg], [seriesAB, seriesBC])[0];
expect(processed.fields.length).toBe(3);
......
......@@ -2,7 +2,9 @@ import { FieldType } from '../../types/dataFrame';
import { DataTransformerID } from './ids';
import { toDataFrame } from '../../dataframe/processDataFrame';
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({
fields: [
......@@ -14,6 +16,10 @@ export const simpleSeriesWithTypes = toDataFrame({
});
describe('Filter Transformer', () => {
beforeAll(() => {
mockTransformationsRegistry([filterFieldsTransformer]);
});
it('filters by include', () => {
const cfg = {
id: DataTransformerID.filterFields,
......
import { DataTransformerID } from './ids';
import { transformDataFrame } from '../transformers';
import { toDataFrame } from '../../dataframe/processDataFrame';
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({
fields: [
......@@ -13,6 +16,10 @@ export const seriesWithNamesToMatch = toDataFrame({
});
describe('filterByName transformer', () => {
beforeAll(() => {
mockTransformationsRegistry([filterFieldsByNameTransformer, filterFieldsTransformer]);
});
it('returns original series if no options provided', () => {
const cfg = {
id: DataTransformerID.filterFields,
......
import { DataTransformerID } from './ids';
import { transformDataFrame } from '../transformers';
import { toDataFrame } from '../../dataframe/processDataFrame';
import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry';
import { filterFramesByRefIdTransformer } from './filterByRefId';
import { transformDataFrame } from '../transformDataFrame';
export const allSeries = [
toDataFrame({
......@@ -18,6 +20,9 @@ export const allSeries = [
];
describe('filterByRefId transformer', () => {
beforeAll(() => {
mockTransformationsRegistry([filterFramesByRefIdTransformer]);
});
it('returns all series if no options provided', () => {
const cfg = {
id: DataTransformerID.filterByRefId,
......
......@@ -6,9 +6,13 @@ import {
toDataFrame,
transformDataFrame,
} from '@grafana/data';
import { OrderFieldsTransformerOptions } from './order';
import { orderFieldsTransformer, OrderFieldsTransformerOptions } from './order';
import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry';
describe('Order Transformer', () => {
beforeAll(() => {
mockTransformationsRegistry([orderFieldsTransformer]);
});
describe('when consistent data is received', () => {
const data = toDataFrame({
name: 'A',
......
......@@ -6,9 +6,14 @@ import {
toDataFrame,
transformDataFrame,
} from '@grafana/data';
import { OrganizeFieldsTransformerOptions } from './organize';
import { organizeFieldsTransformer, OrganizeFieldsTransformerOptions } from './organize';
import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry';
describe('OrganizeFields Transformer', () => {
beforeAll(() => {
mockTransformationsRegistry([organizeFieldsTransformer]);
});
describe('when consistent data is received', () => {
const data = toDataFrame({
name: 'A',
......
import { ReducerID } from '../fieldReducer';
import { DataTransformerID } from './ids';
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({
fields: [
......@@ -11,6 +13,9 @@ const seriesWithValues = toDataFrame({
});
describe('Reducer Transformer', () => {
beforeAll(() => {
mockTransformationsRegistry([reduceTransformer]);
});
it('filters by include', () => {
const cfg = {
id: DataTransformerID.reduce,
......
......@@ -6,9 +6,14 @@ import {
toDataFrame,
transformDataFrame,
} from '@grafana/data';
import { RenameFieldsTransformerOptions } from './rename';
import { RenameFieldsTransformerOptions, renameFieldsTransformer } from './rename';
import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry';
describe('Rename Transformer', () => {
beforeAll(() => {
mockTransformationsRegistry([renameFieldsTransformer]);
});
describe('when consistent data is received', () => {
const data = toDataFrame({
name: 'A',
......
......@@ -6,9 +6,13 @@ import {
toDataFrame,
transformDataFrame,
} from '@grafana/data';
import { SeriesToColumnsOptions } from './seriesToColumns';
import { SeriesToColumnsOptions, seriesToColumnsTransformer } from './seriesToColumns';
import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry';
describe('SeriesToColumns Transformer', () => {
beforeAll(() => {
mockTransformationsRegistry([seriesToColumnsTransformer]);
});
const everySecondSeries = toDataFrame({
name: 'even',
fields: [
......
......@@ -2,16 +2,26 @@ import { DataFrame, Field } from './dataFrame';
import { RegistryItemWithOptions } from '../utils/Registry';
/**
* Immutable data transformation
* Function that transform data frames (AKA transformer)
*/
export type DataTransformer = (data: DataFrame[]) => DataFrame[];
export interface DataTransformerInfo<TOptions = any> extends RegistryItemWithOptions {
/**
* Function that configures transformation and returns a transformer
* @param options
*/
transformer: (options: TOptions) => DataTransformer;
}
export interface DataTransformerConfig<TOptions = any> {
/**
* Unique identifier of transformer
*/
id: string;
/**
* Options to be passed to the transformer
*/
options: TOptions;
}
......
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) => {
switch (variant) {
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(
{
light: tinycolor(from)
.darken(5)
.toString(),
dark: tinycolor(from)
.lighten(4)
.toString(),
dark: theme.colors.gray05,
},
theme.type
) as string;
......
import React, { useContext } from 'react';
import { FilterFieldsByNameTransformerOptions, DataTransformerID, transformersRegistry, KeyValue } from '@grafana/data';
import { TransformerUIProps, TransformerUIRegistyItem } from './types';
import {
DataTransformerID,
FilterFieldsByNameTransformerOptions,
KeyValue,
standardTransformers,
TransformerRegistyItem,
TransformerUIProps,
} from '@grafana/data';
import { ThemeContext } from '../../themes/ThemeContext';
import { css, cx } from 'emotion';
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,
component: FilterByNameTransformerEditor,
transformer: transformersRegistry.get(DataTransformerID.filterFieldsByName),
editor: FilterByNameTransformerEditor,
transformation: standardTransformers.filterFieldsByNameTransformer,
name: 'Filter by name',
description: 'UI for filter by name transformation',
description: 'Filter fields by name',
};
import React, { useContext } from 'react';
import {
FilterFramesByRefIdTransformerOptions,
DataTransformerID,
transformersRegistry,
FilterFramesByRefIdTransformerOptions,
KeyValue,
standardTransformers,
TransformerRegistyItem,
TransformerUIProps,
} from '@grafana/data';
import { TransformerUIProps, TransformerUIRegistyItem } from './types';
import { ThemeContext } from '../../themes/ThemeContext';
import { css, cx } from 'emotion';
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,
component: FilterByRefIdTransformerEditor,
transformer: transformersRegistry.get(DataTransformerID.filterByRefId),
editor: FilterByRefIdTransformerEditor,
transformation: standardTransformers.filterFramesByRefIdTransformer,
name: 'Filter by refId',
description: 'Filter results by refId',
};
import React, { useMemo, useCallback } from 'react';
import React, { useCallback, useMemo } from 'react';
import { css, cx } from 'emotion';
import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd';
import { DragDropContext, Draggable, Droppable, DropResult } from 'react-beautiful-dnd';
import {
DataTransformerID,
transformersRegistry,
createOrderFieldsComparer,
DataFrame,
DataTransformerID,
GrafanaTheme,
createOrderFieldsComparer,
OrganizeFieldsTransformerOptions,
standardTransformers,
TransformerRegistyItem,
TransformerUIProps,
} from '@grafana/data';
import { TransformerUIRegistyItem, TransformerUIProps } from './types';
import { stylesFactory, useTheme } from '../../themes';
import { Button } from '../Button/Button';
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,
component: OrganizeFieldsTransformerEditor,
transformer: transformersRegistry.get(DataTransformerID.organize),
editor: OrganizeFieldsTransformerEditor,
transformation: standardTransformers.organizeFieldsTransformer,
name: 'Organize fields',
description: 'UI for organizing fields',
description: 'Order, filter and rename fields',
};
import React from 'react';
import { StatsPicker } from '../StatsPicker/StatsPicker';
import { ReduceTransformerOptions, DataTransformerID, ReducerID, transformersRegistry } from '@grafana/data';
import { TransformerUIRegistyItem, TransformerUIProps } from './types';
import {
ReduceTransformerOptions,
DataTransformerID,
ReducerID,
standardTransformers,
TransformerRegistyItem,
TransformerUIProps,
} from '@grafana/data';
// TODO: Minimal implementation, needs some <3
export const ReduceTransformerEditor: React.FC<TransformerUIProps<ReduceTransformerOptions>> = ({
options,
onChange,
input,
}) => {
return (
<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,
component: ReduceTransformerEditor,
transformer: transformersRegistry.get(DataTransformerID.reduce),
editor: ReduceTransformerEditor,
transformation: standardTransformers.reduceTransformer,
name: 'Reduce',
description: 'UI for reduce transformation',
description: 'Return a DataFrame with the reduction results',
};
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,
];
});
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 { Select } from '../Select/Select';
import { FullWidthButtonContainer } from '../Button/FullWidthButtonContainer';
import { ComponentSize } from '../../types/size';
interface ValuePickerProps<T> {
/** Label to display on the picker button */
......@@ -14,20 +15,29 @@ interface ValuePickerProps<T> {
options: Array<SelectableValue<T>>;
onChange: (value: SelectableValue<T>) => void;
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);
return (
<>
{!isPicking && (
<FullWidthButtonContainer>
<Button size="sm" icon={icon || 'plus-circle'} onClick={() => setIsPicking(true)} variant={variant}>
const buttonEl = (
<Button size={size || 'sm'} icon={icon || 'plus-circle'} onClick={() => setIsPicking(true)} variant={variant}>
{label}
</Button>
</FullWidthButtonContainer>
)}
);
return (
<>
{!isPicking && (isFullWidth ? <FullWidthButtonContainer>{buttonEl}</FullWidthButtonContainer> : buttonEl)}
{isPicking && (
<Select
......
......@@ -100,7 +100,7 @@ export { DataLinksInlineEditor } from './DataLinks/DataLinksInlineEditor/DataLin
export { DataLinkInput } from './DataLinks/DataLinkInput';
export { DataLinksContextMenu } from './DataLinks/DataLinksContextMenu';
export { SeriesIcon } from './Legend/SeriesIcon';
export { transformersUIRegistry } from './TransformersUI/transformers';
export { JSONFormatter } from './JSONFormatter/JSONFormatter';
export { JsonExplorer } from './JSONFormatter/json_explorer/json_explorer';
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
export { DOMUtil };
export { renderOrCallToRender } from './renderOrCallToRender';
// Exposes standard editors for registries of optionsUi config and panel options UI
export { getStandardFieldConfigs, getStandardOptionEditors } from './standardEditors';
export { renderOrCallToRender } from './renderOrCallToRender';
// Exposes standard transformers for registry of Transformations
export { getStandardTransformers } from './standardTransformers';
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,
standardEditorsRegistry,
standardFieldConfigEditorRegistry,
standardTransformersRegistry,
} from '@grafana/data';
import appEvents from 'app/core/app_events';
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 'app/routes/GrafanaCtrl';
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 { initDevFeatures } from './dev';
......@@ -97,6 +98,7 @@ export class GrafanaApp {
standardEditorsRegistry.setInit(getStandardOptionEditors);
standardFieldConfigEditorRegistry.setInit(getStandardFieldConfigs);
standardTransformersRegistry.setInit(getStandardTransformers);
variableAdapters.setInit(getDefaultVariableAdapters);
app.config(
......
import { css } from 'emotion';
import React from 'react';
import { transformersUIRegistry } from '@grafana/ui';
import { DataTransformerConfig, DataFrame, transformDataFrame, SelectableValue } from '@grafana/data';
import { Button, CustomScrollbar, Select, Container } from '@grafana/ui';
import { Container, CustomScrollbar, ValuePicker } from '@grafana/ui';
import {
DataFrame,
DataTransformerConfig,
SelectableValue,
standardTransformersRegistry,
transformDataFrame,
} from '@grafana/data';
import { TransformationOperationRow } from './TransformationOperationRow';
interface Props {
......@@ -11,13 +15,7 @@ interface Props {
dataFrames: DataFrame[];
}
interface State {
addingTransformation: boolean;
}
export class TransformationsEditor extends React.PureComponent<Props, State> {
state = { addingTransformation: false };
export class TransformationsEditor extends React.PureComponent<Props> {
onTransformationAdd = (selectable: SelectableValue<string>) => {
const { transformations, onChange } = this.props;
onChange([
......@@ -27,7 +25,6 @@ export class TransformationsEditor extends React.PureComponent<Props, State> {
options: {},
},
]);
this.setState({ addingTransformation: false });
};
onTransformationChange = (idx: number, config: DataTransformerConfig) => {
......@@ -45,32 +42,23 @@ export class TransformationsEditor extends React.PureComponent<Props, State> {
};
renderTransformationSelector = () => {
if (!this.state.addingTransformation) {
return null;
}
const availableTransformers = transformersUIRegistry.list().map(t => {
const availableTransformers = standardTransformersRegistry.list().map(t => {
return {
value: t.transformer.id,
label: t.transformer.name,
value: t.transformation.id,
label: t.name,
description: t.description,
};
});
return (
<div
className={css`
margin-bottom: 10px;
max-width: 300px;
`}
>
<Select
<ValuePicker
size="md"
variant="secondary"
label="Add transformation"
options={availableTransformers}
placeholder="Select transformation"
onChange={this.onTransformationAdd}
autoFocus={true}
openMenuOnFocus={true}
isFullWidth={false}
/>
</div>
);
};
......@@ -83,7 +71,7 @@ export class TransformationsEditor extends React.PureComponent<Props, State> {
{transformations.map((t, i) => {
let editor;
const transformationUI = transformersUIRegistry.getIfExists(t.id);
const transformationUI = standardTransformersRegistry.getIfExists(t.id);
if (!transformationUI) {
return null;
}
......@@ -92,8 +80,8 @@ export class TransformationsEditor extends React.PureComponent<Props, State> {
const output = transformDataFrame(transformations.slice(i), input);
if (transformationUI) {
editor = React.createElement(transformationUI.component, {
options: { ...transformationUI.transformer.defaultOptions, ...t.options },
editor = React.createElement(transformationUI.editor, {
options: { ...transformationUI.transformation.defaultOptions, ...t.options },
input,
onChange: (options: any) => {
this.onTransformationChange(i, {
......@@ -130,9 +118,6 @@ export class TransformationsEditor extends React.PureComponent<Props, State> {
</p>
{this.renderTransformationEditors()}
{this.renderTransformationSelector()}
<Button variant="secondary" icon="plus" onClick={() => this.setState({ addingTransformation: true })}>
Add transformation
</Button>
</Container>
</CustomScrollbar>
);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment