Transformers: calculate a new field based on the row values (#23675)

This commit is contained in:
Ryan McKinley
2020-04-20 09:57:04 -07:00
committed by GitHub
parent b669bfdf5f
commit 229176f1b0
9 changed files with 421 additions and 1 deletions

View File

@@ -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',
};

View File

@@ -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,
];
};