From 51f3335534c899010b69e2baf3a773a4c98c4bd6 Mon Sep 17 00:00:00 2001 From: rmawatson <=> Date: Tue, 22 Jul 2025 22:10:55 +0000 Subject: [PATCH] [Feature] add Affix (Prefix/Suffix) formatting option to Format String transformation --- .../transformers/formatString.ts | 5 ++ .../app/core/components/OptionsUI/string.tsx | 9 ++-- .../editors/FormatStringTransformerEditor.tsx | 49 +++++++++++++++++++ public/locales/en-US/grafana.json | 4 +- 4 files changed, 62 insertions(+), 5 deletions(-) diff --git a/packages/grafana-data/src/transformations/transformers/formatString.ts b/packages/grafana-data/src/transformations/transformers/formatString.ts index 15041c0e947..17011ff1eee 100644 --- a/packages/grafana-data/src/transformations/transformers/formatString.ts +++ b/packages/grafana-data/src/transformations/transformers/formatString.ts @@ -18,6 +18,7 @@ export enum FormatStringOutput { KebabCase = 'Kebab Case', Trim = 'Trim', Substring = 'Substring', + Affix = 'Affix' } export interface FormatStringTransformerOptions { @@ -25,6 +26,8 @@ export interface FormatStringTransformerOptions { substringStart: number; substringEnd: number; outputFormat: FormatStringOutput; + stringPrefix: string; + stringSuffix: string; } const splitToCapitalWords = (input: string) => { @@ -60,6 +63,8 @@ export const getFormatStringFunction = (options: FormatStringTransformerOptions) return value.trim(); case FormatStringOutput.Substring: return value.substring(options.substringStart, options.substringEnd); + case FormatStringOutput.Affix: + return (options.stringPrefix ?? "") + value + (options.stringSuffix ?? ""); } }); }; diff --git a/public/app/core/components/OptionsUI/string.tsx b/public/app/core/components/OptionsUI/string.tsx index 8001bf096af..b77fd7c0061 100644 --- a/public/app/core/components/OptionsUI/string.tsx +++ b/public/app/core/components/OptionsUI/string.tsx @@ -6,9 +6,10 @@ import { Input, TextArea } from '@grafana/ui'; interface Props extends StandardEditorProps { suffix?: ReactNode; + preserveWhitespace?: boolean; } -export const StringValueEditor = ({ value, onChange, item, suffix }: Props) => { +export const StringValueEditor = ({ value, onChange, item, suffix, preserveWhitespace}: Props) => { const Component = item.settings?.useTextarea ? TextArea : Input; const onValueChange = useCallback( ( @@ -20,18 +21,18 @@ export const StringValueEditor = ({ value, onChange, item, suffix }: 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 ( diff --git a/public/app/features/transformers/editors/FormatStringTransformerEditor.tsx b/public/app/features/transformers/editors/FormatStringTransformerEditor.tsx index 9bee2e9bd29..64fa0fc75b4 100644 --- a/public/app/features/transformers/editors/FormatStringTransformerEditor.tsx +++ b/public/app/features/transformers/editors/FormatStringTransformerEditor.tsx @@ -11,12 +11,14 @@ import { StandardEditorsRegistryItem, FieldNamePickerConfigSettings, TransformerCategory, + StringFieldConfigSettings, } from '@grafana/data'; import { FormatStringOutput, FormatStringTransformerOptions } from '@grafana/data/internal'; import { t } from '@grafana/i18n'; import { Select, InlineFieldRow, InlineField } from '@grafana/ui'; import { FieldNamePicker } from '@grafana/ui/internal'; import { NumberInput } from 'app/core/components/OptionsUI/NumberInput'; +import { StringValueEditor } from 'app/core/components/OptionsUI/string'; import darkImage from '../images/dark/formatString.svg'; import lightImage from '../images/light/formatString.svg'; @@ -86,8 +88,32 @@ function FormatStringTransfomerEditor({ [onChange, options] ); + const onPrefixChange = useCallback( + (value?: string) => { + onChange({ + ...options, + stringPrefix: value ?? '', + }); + }, + [onChange, options] + ); + + const onSuffixChange = useCallback( + (value?: string) => { + onChange({ + ...options, + stringSuffix: value ?? '', + }); + }, + [onChange, options] + ); + const ops = Object.values(FormatStringOutput).map((value) => ({ label: value, value })); + const dummyStringSettings = { + settings: {}, + } as StandardEditorsRegistryItem; + return ( <> @@ -118,6 +144,29 @@ function FormatStringTransfomerEditor({ )} + {options.outputFormat === FormatStringOutput.Affix && ( + + + + + + + + + + )} ); } diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 2c009a575c4..e2032905f35 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -13052,7 +13052,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": {