Compare commits

...

19 Commits

Author SHA1 Message Date
Kristina Durivage 78c7a875a3 Merge branch 'main' of https://github.com/grafana/grafana into kristina/affix-attempt-donotmerge
# Conflicts:
#	.betterer.results
#	public/app/core/components/OptionsUI/string.tsx
2025-10-21 14:51:12 -05:00
Kristina Durivage 933975b680 Use context.interpolate function 2025-08-02 13:19:52 -05:00
Kristina Durivage 15937aab9c Merge branch 'main' of https://github.com/grafana/grafana into kristina/affix-attempt-donotmerge 2025-08-02 11:24:58 -05:00
Kristina Durivage 2321bbdda1 Merge branch 'main' of https://github.com/grafana/grafana into kristina/affix-attempt-donotmerge 2025-08-01 11:42:05 -05:00
Kristina Durivage c36516d85c Merge branch 'main' of https://github.com/grafana/grafana into kristina/affix-attempt-donotmerge 2025-07-30 08:14:24 -05:00
Kristina Durivage 2e09b98b67 Bring in replaceFn, use it with fieldVars 2025-07-27 11:30:14 -05:00
Kristina Durivage de072a25e7 Remove parsing logic and affix function 2025-07-25 13:31:15 -05:00
Kristina Durivage 44acbb2977 Merge branch 'main' of https://github.com/grafana/grafana into affix-feature-branch
# Conflicts:
#	.betterer.results
2025-07-25 08:33:31 -05:00
Isabel Matwawana 43a72f80ba Pushed changes from ts file to md file 2025-07-25 09:21:45 -04:00
rmawatson d70f02203d Update public/app/features/transformers/docs/content.ts
Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com>
2025-07-25 15:16:41 +02:00
rmawatson c15f104215 Update content.ts 2025-07-25 02:59:27 +02:00
rmawatson 5536fcc3fb Updated documentation 2025-07-25 02:56:26 +02:00
rmawatson 8422d1f0f0 Added padding and factored out the templating 2025-07-24 23:33:52 +00:00
Isabel Matwawana e2da156c41 Pushed change from ts file to md file 2025-07-24 10:18:00 -04:00
rmawatson d5d4d08059 [Feature] Add 'Template expression' option to 'Add field from calculation' 2025-07-24 01:24:41 +00:00
rmawatson e5db6687bf Added Template Expression to 'Add field from calculation' 2025-07-24 01:16:24 +00:00
rmawatson 05bde8509a Added ability to paste the value of other fields using {field} syntax 2025-07-23 03:06:28 +00:00
rmawatson 8311379ff7 added documentation for Affix 2025-07-22 22:56:31 +00:00
rmawatson 51f3335534 [Feature] add Affix (Prefix/Suffix) formatting option to Format String transformation 2025-07-22 22:36:32 +00:00
8 changed files with 148 additions and 11 deletions
@@ -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.
@@ -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')}
@@ -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>
</>
);
};
+9 -2
View File
@@ -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": {