Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 78c7a875a3 | |||
| 933975b680 | |||
| 15937aab9c | |||
| 2321bbdda1 | |||
| c36516d85c | |||
| 2e09b98b67 | |||
| de072a25e7 | |||
| 44acbb2977 | |||
| 43a72f80ba | |||
| d70f02203d | |||
| c15f104215 | |||
| 5536fcc3fb | |||
| 8422d1f0f0 | |||
| e2da156c41 | |||
| d5d4d08059 | |||
| e5db6687bf | |||
| 05bde8509a | |||
| 8311379ff7 | |||
| 51f3335534 |
+6
@@ -197,6 +197,12 @@ Use this transformation to add a new field calculated from two other fields. Eac
|
||||
- **Stddev** - Calculates the moving standard deviation.
|
||||
- **Variance** - Calculates the moving variance.
|
||||
- **Row index** - Insert a field with the row index.
|
||||
- **Template expression** - Insert a field with the value generated from a template expression. The expression can paste the value of other fields by using the {field} syntax, with optional modifiers for padding.
|
||||
- **No alignment** - Pastes the contents of 'field': {field}
|
||||
- **Left alignment** - Pastes the contents of 'field' aligned left with default padding character, padded to 10 characters: {field:<10}
|
||||
- **Right alignment** - Pastes the contents of 'field' aligned right with default padding character, padded to 10 characters: {field:>10}
|
||||
- **Center alignment** - Pastes the contents of 'field' center aligned with default padding character, padded to 10 characters: {field:^10}
|
||||
- **Custom padding character** - Pastes the contents of 'field' center aligned with an underscore padding character, padded to 10 characters: {field:\_^10}
|
||||
- **Field name** - Select the names of fields you want to use in the calculation for the new field.
|
||||
- **Calculation** - If you select **Reduce row** mode, then the **Calculation** field appears. Click in the field to see a list of calculation choices you can use to create the new field. For information about available calculations, refer to [Calculation types][].
|
||||
- **Operation** - If you select **Binary operation** or **Unary operation** mode, then the **Operation** fields appear. These fields allow you to apply basic math operations on values in a single row from selected fields. You can also use numerical values for binary operations.
|
||||
|
||||
@@ -3,6 +3,7 @@ import { map } from 'rxjs/operators';
|
||||
|
||||
import { getTimeField } from '../../dataframe/processDataFrame';
|
||||
import { getFieldDisplayName } from '../../field/fieldState';
|
||||
import { ScopedVars } from '../../types/ScopedVars';
|
||||
import { NullValueMode } from '../../types/data';
|
||||
import { DataFrame, FieldType, Field } from '../../types/dataFrame';
|
||||
import { DataTransformContext, DataTransformerInfo } from '../../types/transformations';
|
||||
@@ -23,6 +24,7 @@ export enum CalculateFieldMode {
|
||||
BinaryOperation = 'binary',
|
||||
UnaryOperation = 'unary',
|
||||
Index = 'index',
|
||||
TemplateExpression = 'templateExpression',
|
||||
}
|
||||
|
||||
export enum WindowSizeMode {
|
||||
@@ -72,6 +74,11 @@ interface IndexOptions {
|
||||
asPercentile: boolean;
|
||||
}
|
||||
|
||||
interface TemplateExpressionOptions {
|
||||
expression: string;
|
||||
replaceFn: Function;
|
||||
}
|
||||
|
||||
const defaultReduceOptions: ReduceOptions = {
|
||||
reducer: ReducerID.sum,
|
||||
};
|
||||
@@ -106,6 +113,7 @@ export interface CalculateFieldTransformerOptions {
|
||||
binary?: BinaryOptions;
|
||||
unary?: UnaryOptions;
|
||||
index?: IndexOptions;
|
||||
template?: TemplateExpressionOptions;
|
||||
|
||||
// Remove other fields
|
||||
replaceFields?: boolean;
|
||||
@@ -113,6 +121,7 @@ export interface CalculateFieldTransformerOptions {
|
||||
// Output field properties
|
||||
alias?: string; // The output field name
|
||||
// TODO: config?: FieldConfig; or maybe field overrides? since the UI exists
|
||||
returnType?: FieldType;
|
||||
}
|
||||
|
||||
type ValuesCreator = (data: DataFrame) => unknown[] | undefined;
|
||||
@@ -249,6 +258,33 @@ export const calculateFieldTransformer: DataTransformerInfo<CalculateFieldTransf
|
||||
fields: options.replaceFields ? [f] : [...frame.fields, f],
|
||||
};
|
||||
});
|
||||
case CalculateFieldMode.TemplateExpression:
|
||||
if (options.template?.expression !== undefined) {
|
||||
return data.map((frame) => {
|
||||
const newFieldVals = Array.from({ length: frame.length }, (_, i) => {
|
||||
const fieldVars: ScopedVars = {};
|
||||
frame.fields.forEach((field) => {
|
||||
fieldVars[field.name] = {
|
||||
value: field.values[i],
|
||||
};
|
||||
});
|
||||
const replaced = ctx.interpolate(options.template!.expression, fieldVars);
|
||||
return replaced;
|
||||
});
|
||||
const f: Field = {
|
||||
name: options.alias ?? 'Field',
|
||||
type: FieldType.string,
|
||||
values: newFieldVals,
|
||||
config: {},
|
||||
};
|
||||
return {
|
||||
...frame,
|
||||
fields: options.replaceFields ? [f] : [...frame.fields, f],
|
||||
};
|
||||
});
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing configured
|
||||
@@ -269,7 +305,7 @@ export const calculateFieldTransformer: DataTransformerInfo<CalculateFieldTransf
|
||||
|
||||
const field: Field = {
|
||||
name: getNameFromOptions(options),
|
||||
type: FieldType.number,
|
||||
type: options.returnType ?? FieldType.number,
|
||||
config: {},
|
||||
values,
|
||||
};
|
||||
@@ -713,6 +749,8 @@ export function getNameFromOptions(options: CalculateFieldTransformerOptions) {
|
||||
break;
|
||||
case CalculateFieldMode.Index:
|
||||
return 'Row';
|
||||
case CalculateFieldMode.TemplateExpression:
|
||||
return 'Field';
|
||||
}
|
||||
|
||||
return 'math';
|
||||
|
||||
@@ -25,6 +25,8 @@ export interface FormatStringTransformerOptions {
|
||||
substringStart: number;
|
||||
substringEnd: number;
|
||||
outputFormat: FormatStringOutput;
|
||||
stringPrefix: string;
|
||||
stringSuffix: string;
|
||||
}
|
||||
|
||||
const splitToCapitalWords = (input: string) => {
|
||||
@@ -36,8 +38,8 @@ const splitToCapitalWords = (input: string) => {
|
||||
};
|
||||
|
||||
export const getFormatStringFunction = (options: FormatStringTransformerOptions) => {
|
||||
return (field: Field) =>
|
||||
field.values.map((value: string) => {
|
||||
return (field: Field, allFields: Field[]) =>
|
||||
field.values.map((value: string, index: number) => {
|
||||
switch (options.outputFormat) {
|
||||
case FormatStringOutput.UpperCase:
|
||||
return value.toUpperCase();
|
||||
@@ -106,12 +108,12 @@ export const formatStringTransformer: DataTransformerInfo<FormatStringTransforme
|
||||
* @internal
|
||||
*/
|
||||
export const createStringFormatter =
|
||||
(fieldMatches: FieldMatcher, formatStringFunction: (field: Field) => string[]) =>
|
||||
(fieldMatches: FieldMatcher, formatStringFunction: (field: Field, allFields: Field[]) => string[]) =>
|
||||
(frame: DataFrame, allFrames: DataFrame[]) => {
|
||||
return frame.fields.map((field) => {
|
||||
// Find the configured field
|
||||
if (fieldMatches(field, frame, allFrames)) {
|
||||
const newVals = formatStringFunction(field);
|
||||
const newVals = formatStringFunction(field, frame.fields);
|
||||
|
||||
return {
|
||||
...field,
|
||||
|
||||
@@ -6,9 +6,10 @@ import { Input, TextArea } from '@grafana/ui';
|
||||
|
||||
interface Props extends StandardEditorProps<string, StringFieldConfigSettings> {
|
||||
suffix?: ReactNode;
|
||||
preserveWhitespace?: boolean;
|
||||
}
|
||||
|
||||
export const StringValueEditor = ({ value, onChange, item, suffix, id }: Props) => {
|
||||
export const StringValueEditor = ({ value, onChange, item, suffix, preserveWhitespace, id }: Props) => {
|
||||
const Component = item.settings?.useTextarea ? TextArea : Input;
|
||||
const onValueChange = useCallback(
|
||||
(
|
||||
@@ -20,18 +21,18 @@ export const StringValueEditor = ({ value, onChange, item, suffix, id }: Props)
|
||||
if ('key' in e) {
|
||||
// handling keyboard event
|
||||
if (e.key === 'Enter' && !item.settings?.useTextarea) {
|
||||
nextValue = e.currentTarget.value.trim();
|
||||
nextValue = (preserveWhitespace ?? false) ? e.currentTarget.value : e.currentTarget.value.trim();
|
||||
}
|
||||
} else {
|
||||
// handling blur event
|
||||
nextValue = e.currentTarget.value.trim();
|
||||
nextValue = (preserveWhitespace ?? false) ? e.currentTarget.value : e.currentTarget.value.trim();
|
||||
}
|
||||
if (nextValue === value) {
|
||||
return; // no change
|
||||
}
|
||||
onChange(nextValue === '' ? undefined : nextValue);
|
||||
},
|
||||
[value, item.settings?.useTextarea, onChange]
|
||||
[value, item.settings?.useTextarea, onChange, preserveWhitespace]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -61,6 +61,12 @@ Use this transformation to add a new field calculated from two other fields. Eac
|
||||
- **Stddev** - Calculates the moving standard deviation.
|
||||
- **Variance** - Calculates the moving variance.
|
||||
- **Row index** - Insert a field with the row index.
|
||||
- **Template expression** - Insert a field with the value generated from a template expression. The expression can paste the value of other fields by using the {field} syntax, with optional modifiers for padding.
|
||||
- **No alignment** - Pastes the contents of 'field': {field}
|
||||
- **Left alignment** - Pastes the contents of 'field' aligned left with default padding character, padded to 10 characters: {field:<10}
|
||||
- **Right alignment** - Pastes the contents of 'field' aligned right with default padding character, padded to 10 characters: {field:>10}
|
||||
- **Center alignment** - Pastes the contents of 'field' center aligned with default padding character, padded to 10 characters: {field:^10}
|
||||
- **Custom padding character** - Pastes the contents of 'field' center aligned with an underscore padding character, padded to 10 characters: {field:_^10}
|
||||
- **Field name** - Select the names of fields you want to use in the calculation for the new field.
|
||||
- **Calculation** - If you select **Reduce row** mode, then the **Calculation** field appears. Click in the field to see a list of calculation choices you can use to create the new field. For information about available calculations, refer to [Calculation types][].
|
||||
- **Operation** - If you select **Binary operation** or **Unary operation** mode, then the **Operation** fields appear. These fields allow you to apply basic math operations on values in a single row from selected fields. You can also use numerical values for binary operations.
|
||||
|
||||
+13
@@ -34,6 +34,7 @@ import { BinaryOperationOptionsEditor } from './BinaryOperationOptionsEditor';
|
||||
import { CumulativeOptionsEditor } from './CumulativeOptionsEditor';
|
||||
import { IndexOptionsEditor } from './IndexOptionsEditor';
|
||||
import { ReduceRowOptionsEditor } from './ReduceRowOptionsEditor';
|
||||
import { TemplateExpressionOptionsEditor } from './TemplateExpressionOptionsEditor';
|
||||
import { UnaryOperationEditor } from './UnaryOperationEditor';
|
||||
import { WindowOptionsEditor } from './WindowOptionsEditor';
|
||||
import { LABEL_WIDTH } from './constants';
|
||||
@@ -89,6 +90,11 @@ export const CalculateFieldTransformerEditor = (props: CalculateFieldTransformer
|
||||
);
|
||||
}
|
||||
|
||||
calculationModes.push({
|
||||
value: CalculateFieldMode.TemplateExpression,
|
||||
label: t('transformers.calculate-field-transformer-editor.label.template-expression', 'Template expression'),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const ctx = { interpolate: (v: string) => v };
|
||||
const subscription = of(input)
|
||||
@@ -244,6 +250,13 @@ export const CalculateFieldTransformerEditor = (props: CalculateFieldTransformer
|
||||
{mode === CalculateFieldMode.Index && (
|
||||
<IndexOptionsEditor options={options} onChange={props.onChange}></IndexOptionsEditor>
|
||||
)}
|
||||
{mode === CalculateFieldMode.TemplateExpression && (
|
||||
<TemplateExpressionOptionsEditor
|
||||
input={input}
|
||||
options={options}
|
||||
onChange={props.onChange}
|
||||
></TemplateExpressionOptionsEditor>
|
||||
)}
|
||||
<InlineField
|
||||
labelWidth={LABEL_WIDTH}
|
||||
label={t('transformers.calculate-field-transformer-editor.label-alias', 'Alias')}
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { TransformerUIProps, StringFieldConfigSettings, StandardEditorsRegistryItem } from '@grafana/data';
|
||||
import { CalculateFieldMode, CalculateFieldTransformerOptions } from '@grafana/data/internal';
|
||||
import { t } from '@grafana/i18n';
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
import { InlineField, InlineFieldRow } from '@grafana/ui';
|
||||
import { StringValueEditor } from 'app/core/components/OptionsUI/string';
|
||||
|
||||
import { LABEL_WIDTH } from './constants';
|
||||
|
||||
// todo: check why not-scenes throws an error here https://github.com/grafana/grafana/blob/main/public/app/features/templating/template_srv.ts#L297
|
||||
|
||||
export const TemplateExpressionOptionsEditor = ({
|
||||
input,
|
||||
options,
|
||||
onChange,
|
||||
}: TransformerUIProps<CalculateFieldTransformerOptions>) => {
|
||||
const replaceFn = getTemplateSrv().replace;
|
||||
const onTemplateExpressionChanged = useCallback(
|
||||
(value?: string) => {
|
||||
onChange({
|
||||
...options,
|
||||
mode: CalculateFieldMode.TemplateExpression,
|
||||
template: {
|
||||
expression: value ?? '',
|
||||
replaceFn,
|
||||
},
|
||||
});
|
||||
},
|
||||
[onChange, options, replaceFn]
|
||||
);
|
||||
|
||||
const dummyStringSettings: StandardEditorsRegistryItem<string, StringFieldConfigSettings> = {
|
||||
id: '',
|
||||
name: '',
|
||||
description: '',
|
||||
editor: StringValueEditor,
|
||||
settings: {},
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<InlineFieldRow>
|
||||
<InlineField
|
||||
labelWidth={LABEL_WIDTH}
|
||||
label={t('transformers.template-expression-options-editor.label-expression', 'Expression')}
|
||||
tooltip={t(
|
||||
'transformers.template-expression-options-editor.tooltip-transform-template-expression',
|
||||
'Transform a template expression into a field value'
|
||||
)}
|
||||
>
|
||||
<StringValueEditor
|
||||
context={{ data: input }}
|
||||
value={options.template?.expression ?? ''}
|
||||
onChange={onTemplateExpressionChanged}
|
||||
item={dummyStringSettings}
|
||||
preserveWhitespace={true}
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -13263,13 +13263,18 @@
|
||||
"placeholder-field-or-number": "Field or number",
|
||||
"placeholder-fields-or-number": "Field(s) or number"
|
||||
},
|
||||
"template-expression-options-editor": {
|
||||
"label-expression:": "Expression",
|
||||
"tooltip-transform-template-expression": "Transform a template expression into a field value"
|
||||
},
|
||||
"calculate-field-transformer-editor": {
|
||||
"calculation-modes": {
|
||||
"label": {
|
||||
"binary-operation": "Binary operation",
|
||||
"reduce-row": "Reduce row",
|
||||
"row-index": "Row index",
|
||||
"unary-operation": "Unary operation"
|
||||
"unary-operation": "Unary operation",
|
||||
"template-expression": "Template expression"
|
||||
}
|
||||
},
|
||||
"label": {
|
||||
@@ -13482,7 +13487,9 @@
|
||||
},
|
||||
"label-field": "Field",
|
||||
"label-format": "Format",
|
||||
"label-substring-range": "Substring range"
|
||||
"label-substring-range": "Substring range",
|
||||
"label-substring-prefix": "Prefix",
|
||||
"label-substring-suffix": "Suffix"
|
||||
},
|
||||
"format-string-transformer-editor": {
|
||||
"description": {
|
||||
|
||||
Reference in New Issue
Block a user