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 './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';
|
||||
|
@ -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 { 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 };
|
||||
};
|
||||
|
@ -1,7 +1,8 @@
|
||||
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,
|
||||
|
@ -1,7 +1,10 @@
|
||||
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,
|
||||
|
@ -1,6 +1,8 @@
|
||||
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',
|
||||
|
@ -1,7 +1,9 @@
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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) => {
|
||||
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;
|
||||
|
@ -1,6 +1,12 @@
|
||||
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',
|
||||
};
|
||||
|
@ -1,11 +1,12 @@
|
||||
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',
|
||||
};
|
||||
|
@ -1,15 +1,16 @@
|
||||
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,
|
||||
DataFrame,
|
||||
GrafanaTheme,
|
||||
createOrderFieldsComparer,
|
||||
DataFrame,
|
||||
DataTransformerID,
|
||||
GrafanaTheme,
|
||||
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',
|
||||
};
|
||||
|
@ -1,13 +1,18 @@
|
||||
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',
|
||||
};
|
||||
|
@ -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 { 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);
|
||||
|
||||
const buttonEl = (
|
||||
<Button size={size || 'sm'} icon={icon || 'plus-circle'} onClick={() => setIsPicking(true)} variant={variant}>
|
||||
{label}
|
||||
</Button>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{!isPicking && (
|
||||
<FullWidthButtonContainer>
|
||||
<Button size="sm" icon={icon || 'plus-circle'} onClick={() => setIsPicking(true)} variant={variant}>
|
||||
{label}
|
||||
</Button>
|
||||
</FullWidthButtonContainer>
|
||||
)}
|
||||
{!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';
|
||||
|
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,
|
||||
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(
|
||||
|
@ -1,8 +1,12 @@
|
||||
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
|
||||
options={availableTransformers}
|
||||
placeholder="Select transformation"
|
||||
onChange={this.onTransformationAdd}
|
||||
autoFocus={true}
|
||||
openMenuOnFocus={true}
|
||||
/>
|
||||
</div>
|
||||
<ValuePicker
|
||||
size="md"
|
||||
variant="secondary"
|
||||
label="Add transformation"
|
||||
options={availableTransformers}
|
||||
onChange={this.onTransformationAdd}
|
||||
isFullWidth={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user