mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Transformers: calculate a new field based on the row values (#23675)
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
import React, { useContext, ChangeEvent } from 'react';
|
||||
import {
|
||||
DataTransformerID,
|
||||
CalculateFieldTransformerOptions,
|
||||
KeyValue,
|
||||
standardTransformers,
|
||||
TransformerRegistyItem,
|
||||
TransformerUIProps,
|
||||
FieldType,
|
||||
ReducerID,
|
||||
fieldReducers,
|
||||
} from '@grafana/data';
|
||||
import { ThemeContext } from '../../themes/ThemeContext';
|
||||
import { css } from 'emotion';
|
||||
import { InlineList } from '../List/InlineList';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { Label } from '../Forms/Label';
|
||||
import { StatsPicker } from '../StatsPicker/StatsPicker';
|
||||
import { Switch } from '../Switch/Switch';
|
||||
import { Input } from '../Input/Input';
|
||||
|
||||
interface CalculateFieldTransformerEditorProps extends TransformerUIProps<CalculateFieldTransformerOptions> {}
|
||||
|
||||
interface CalculateFieldTransformerEditorState {
|
||||
include: string;
|
||||
names: string[];
|
||||
selected: string[];
|
||||
}
|
||||
|
||||
export class CalculateFieldTransformerEditor extends React.PureComponent<
|
||||
CalculateFieldTransformerEditorProps,
|
||||
CalculateFieldTransformerEditorState
|
||||
> {
|
||||
constructor(props: CalculateFieldTransformerEditorProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
include: props.options.include || '',
|
||||
names: [],
|
||||
selected: [],
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.initOptions();
|
||||
}
|
||||
|
||||
private initOptions() {
|
||||
const { input, options } = this.props;
|
||||
const configuredOptions = options.include ? options.include.split('|') : [];
|
||||
|
||||
const allNames: string[] = [];
|
||||
const byName: KeyValue<boolean> = {};
|
||||
for (const frame of input) {
|
||||
for (const field of frame.fields) {
|
||||
if (field.type !== FieldType.number) {
|
||||
continue;
|
||||
}
|
||||
if (!byName[field.name]) {
|
||||
byName[field.name] = true;
|
||||
allNames.push(field.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (configuredOptions.length) {
|
||||
const options: string[] = [];
|
||||
const selected: string[] = [];
|
||||
for (const v of allNames) {
|
||||
if (configuredOptions.includes(v)) {
|
||||
selected.push(v);
|
||||
}
|
||||
options.push(v);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
names: options,
|
||||
selected: selected,
|
||||
});
|
||||
} else {
|
||||
this.setState({ names: allNames, selected: [] });
|
||||
}
|
||||
}
|
||||
|
||||
onFieldToggle = (fieldName: string) => {
|
||||
const { selected } = this.state;
|
||||
if (selected.indexOf(fieldName) > -1) {
|
||||
this.onChange(selected.filter(s => s !== fieldName));
|
||||
} else {
|
||||
this.onChange([...selected, fieldName]);
|
||||
}
|
||||
};
|
||||
|
||||
onChange = (selected: string[]) => {
|
||||
this.setState({ selected });
|
||||
this.props.onChange({
|
||||
...this.props.options,
|
||||
include: selected.join('|'),
|
||||
});
|
||||
};
|
||||
|
||||
onToggleReplaceFields = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
const { options } = this.props;
|
||||
this.props.onChange({
|
||||
...options,
|
||||
replaceFields: !options.replaceFields,
|
||||
});
|
||||
};
|
||||
|
||||
onAliasChanged = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
const { options } = this.props;
|
||||
this.props.onChange({
|
||||
...options,
|
||||
alias: evt.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
onStatsChange = (stats: string[]) => {
|
||||
this.props.onChange({
|
||||
...this.props.options,
|
||||
reducer: stats.length ? (stats[0] as ReducerID) : ReducerID.sum,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { options } = this.props;
|
||||
const { names, selected } = this.state;
|
||||
const reducer = fieldReducers.get(options.reducer);
|
||||
return (
|
||||
<div>
|
||||
<Label>Numeric Fields</Label>
|
||||
<InlineList
|
||||
items={names}
|
||||
renderItem={(o, i) => {
|
||||
return (
|
||||
<span
|
||||
className={css`
|
||||
margin-right: ${i === names.length - 1 ? '0' : '10px'};
|
||||
`}
|
||||
>
|
||||
<FilterPill
|
||||
onClick={() => {
|
||||
this.onFieldToggle(o);
|
||||
}}
|
||||
label={o}
|
||||
selected={selected.indexOf(o) > -1}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Label>Calculation</Label>
|
||||
<StatsPicker stats={[options.reducer]} onChange={this.onStatsChange} defaultStat={ReducerID.sum} />
|
||||
<Label>Alias</Label>
|
||||
<Input value={options.alias} placeholder={reducer.name} onChange={this.onAliasChanged} />
|
||||
|
||||
<Label>Replace all fields</Label>
|
||||
<Switch checked={options.replaceFields} onChange={this.onToggleReplaceFields} />
|
||||
|
||||
{/* nullValueMode?: NullValueMode; */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface FilterPillProps {
|
||||
selected: boolean;
|
||||
label: string;
|
||||
onClick: React.MouseEventHandler<HTMLElement>;
|
||||
}
|
||||
const FilterPill: React.FC<FilterPillProps> = ({ label, selected, onClick }) => {
|
||||
const theme = useContext(ThemeContext);
|
||||
return (
|
||||
<div
|
||||
className={css`
|
||||
padding: ${theme.spacing.xxs} ${theme.spacing.sm};
|
||||
color: white;
|
||||
background: ${selected ? theme.palette.blue95 : theme.palette.blue77};
|
||||
border-radius: 16px;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{selected && (
|
||||
<Icon
|
||||
className={css`
|
||||
margin-right: 4px;
|
||||
`}
|
||||
name="check"
|
||||
/>
|
||||
)}
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const calculateFieldTransformRegistryItem: TransformerRegistyItem<CalculateFieldTransformerOptions> = {
|
||||
id: DataTransformerID.calculateField,
|
||||
editor: CalculateFieldTransformerEditor,
|
||||
transformation: standardTransformers.calculateFieldTransformer,
|
||||
name: 'Add field from calculation',
|
||||
description: 'Use the row values to calculate a new field',
|
||||
};
|
||||
@@ -4,6 +4,7 @@ import { filterFieldsByNameTransformRegistryItem } from '../components/Transform
|
||||
import { filterFramesByRefIdTransformRegistryItem } from '../components/TransformersUI/FilterByRefIdTransformerEditor';
|
||||
import { organizeFieldsTransformRegistryItem } from '../components/TransformersUI/OrganizeFieldsTransformerEditor';
|
||||
import { seriesToFieldsTransformerRegistryItem } from '../components/TransformersUI/SeriesToFieldsTransformerEditor';
|
||||
import { calculateFieldTransformRegistryItem } from '../components/TransformersUI/CalculateFieldTransformerEditor';
|
||||
|
||||
export const getStandardTransformers = (): Array<TransformerRegistyItem<any>> => {
|
||||
return [
|
||||
@@ -12,5 +13,6 @@ export const getStandardTransformers = (): Array<TransformerRegistyItem<any>> =>
|
||||
filterFramesByRefIdTransformRegistryItem,
|
||||
organizeFieldsTransformRegistryItem,
|
||||
seriesToFieldsTransformerRegistryItem,
|
||||
calculateFieldTransformRegistryItem,
|
||||
];
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user