From 721988e931929923a99fa1ab7e1ae565c647d5f1 Mon Sep 17 00:00:00 2001 From: Oscar Kilhed Date: Fri, 10 Nov 2023 16:12:34 +0100 Subject: [PATCH] Transformations: break add field from calculation transformation UI into sub components (#77874) * break out subcomponents * prettify * break out editors into separate files * update betterer since this is only moving around lint issues * Fix import --- .betterer.results | 12 +- .../CalculateFieldTransformerEditor.tsx | 677 ------------------ .../BinaryOperationOptionsEditor.tsx | 102 +++ .../CalculateFieldTransformerEditor.tsx | 238 ++++++ .../CumulativeOptionsEditor.tsx | 66 ++ .../IndexOptionsEditor.tsx | 30 + .../ReduceRowOptionsEditor.tsx | 79 ++ .../UnaryOperationEditor.tsx | 76 ++ .../WindowOptionsEditor.tsx | 140 ++++ .../constants.ts | 1 + .../CalculateFieldTransformerEditor/index.ts | 6 + 11 files changed, 746 insertions(+), 681 deletions(-) delete mode 100644 public/app/features/transformers/editors/CalculateFieldTransformerEditor.tsx create mode 100644 public/app/features/transformers/editors/CalculateFieldTransformerEditor/BinaryOperationOptionsEditor.tsx create mode 100644 public/app/features/transformers/editors/CalculateFieldTransformerEditor/CalculateFieldTransformerEditor.tsx create mode 100644 public/app/features/transformers/editors/CalculateFieldTransformerEditor/CumulativeOptionsEditor.tsx create mode 100644 public/app/features/transformers/editors/CalculateFieldTransformerEditor/IndexOptionsEditor.tsx create mode 100644 public/app/features/transformers/editors/CalculateFieldTransformerEditor/ReduceRowOptionsEditor.tsx create mode 100644 public/app/features/transformers/editors/CalculateFieldTransformerEditor/UnaryOperationEditor.tsx create mode 100644 public/app/features/transformers/editors/CalculateFieldTransformerEditor/WindowOptionsEditor.tsx create mode 100644 public/app/features/transformers/editors/CalculateFieldTransformerEditor/constants.ts create mode 100644 public/app/features/transformers/editors/CalculateFieldTransformerEditor/index.ts diff --git a/.betterer.results b/.betterer.results index fe83a03e916..82b3f962a27 100644 --- a/.betterer.results +++ b/.betterer.results @@ -5083,12 +5083,16 @@ exports[`better eslint`] = { "public/app/features/transformers/configFromQuery/ConfigFromQueryTransformerEditor.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] ], - "public/app/features/transformers/editors/CalculateFieldTransformerEditor.tsx:5381": [ + "public/app/features/transformers/editors/CalculateFieldTransformerEditor/CumulativeOptionsEditor.tsx:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/features/transformers/editors/CalculateFieldTransformerEditor/ReduceRowOptionsEditor.tsx:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], + "public/app/features/transformers/editors/CalculateFieldTransformerEditor/WindowOptionsEditor.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], - [0, 0, 0, "Do not use any type assertions.", "2"], - [0, 0, 0, "Do not use any type assertions.", "3"], - [0, 0, 0, "Do not use any type assertions.", "4"] + [0, 0, 0, "Do not use any type assertions.", "2"] ], "public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] diff --git a/public/app/features/transformers/editors/CalculateFieldTransformerEditor.tsx b/public/app/features/transformers/editors/CalculateFieldTransformerEditor.tsx deleted file mode 100644 index 17122018def..00000000000 --- a/public/app/features/transformers/editors/CalculateFieldTransformerEditor.tsx +++ /dev/null @@ -1,677 +0,0 @@ -import { defaults } from 'lodash'; -import React, { ChangeEvent, useEffect, useState } from 'react'; -import { identity, of, OperatorFunction } from 'rxjs'; -import { map } from 'rxjs/operators'; - -import { - BinaryOperationID, - binaryOperators, - unaryOperators, - DataFrame, - DataTransformerID, - FieldType, - getFieldDisplayName, - KeyValue, - ReducerID, - SelectableValue, - standardTransformers, - TransformerRegistryItem, - TransformerUIProps, - TransformerCategory, - UnaryOperationID, -} from '@grafana/data'; -import { - BinaryOptions, - UnaryOptions, - CalculateFieldMode, - WindowAlignment, - CalculateFieldTransformerOptions, - getNameFromOptions, - IndexOptions, - ReduceOptions, - CumulativeOptions, - WindowOptions, - WindowSizeMode, - defaultWindowOptions, -} from '@grafana/data/src/transformations/transformers/calculateField'; -import { getTemplateSrv, config as cfg } from '@grafana/runtime'; -import { - FilterPill, - HorizontalGroup, - InlineField, - InlineFieldRow, - InlineLabel, - InlineSwitch, - Input, - RadioButtonGroup, - Select, - StatsPicker, -} from '@grafana/ui'; -import { NumberInput } from 'app/core/components/OptionsUI/NumberInput'; - -import { getTransformationContent } from '../docs/getTransformationContent'; - -interface CalculateFieldTransformerEditorProps extends TransformerUIProps {} - -interface CalculateFieldTransformerEditorState { - names: string[]; - selected: string[]; -} - -const calculationModes = [ - { value: CalculateFieldMode.BinaryOperation, label: 'Binary operation' }, - { value: CalculateFieldMode.UnaryOperation, label: 'Unary operation' }, - { value: CalculateFieldMode.ReduceRow, label: 'Reduce row' }, - { value: CalculateFieldMode.Index, label: 'Row index' }, -]; - -if (cfg.featureToggles.addFieldFromCalculationStatFunctions) { - calculationModes.push( - { value: CalculateFieldMode.CumulativeFunctions, label: 'Cumulative functions' }, - { value: CalculateFieldMode.WindowFunctions, label: 'Window functions' } - ); -} - -const okTypes = new Set([FieldType.time, FieldType.number, FieldType.string]); - -const labelWidth = 16; - -export const CalculateFieldTransformerEditor = (props: CalculateFieldTransformerEditorProps) => { - const { options, onChange, input } = props; - const configuredOptions = options?.reduce?.include; - - const [state, setState] = useState({ names: [], selected: [] }); - - useEffect(() => { - const ctx = { interpolate: (v: string) => v }; - const subscription = of(input) - .pipe( - standardTransformers.ensureColumnsTransformer.operator(null, ctx), - extractAllNames(), - getVariableNames(), - extractNamesAndSelected(configuredOptions || []) - ) - .subscribe(({ selected, names }) => { - setState({ names, selected }); - }); - return () => { - subscription.unsubscribe(); - }; - }, [input, configuredOptions]); - - const getVariableNames = (): OperatorFunction => { - if (!cfg.featureToggles.transformationsVariableSupport) { - return identity; - } - const templateSrv = getTemplateSrv(); - return (source) => - source.pipe( - map((input) => { - input.push(...templateSrv.getVariables().map((v) => '$' + v.name)); - return input; - }) - ); - }; - - const extractAllNames = (): OperatorFunction => { - return (source) => - source.pipe( - map((input) => { - const allNames: string[] = []; - const byName: KeyValue = {}; - - for (const frame of input) { - for (const field of frame.fields) { - if (!okTypes.has(field.type)) { - continue; - } - - const displayName = getFieldDisplayName(field, frame, input); - - if (!byName[displayName]) { - byName[displayName] = true; - allNames.push(displayName); - } - } - } - - return allNames; - }) - ); - }; - - const extractNamesAndSelected = ( - configuredOptions: string[] - ): OperatorFunction => { - return (source) => - source.pipe( - map((allNames) => { - if (!configuredOptions.length) { - return { names: allNames, selected: [] }; - } - - const names: string[] = []; - const selected: string[] = []; - - for (const v of allNames) { - if (configuredOptions.includes(v)) { - selected.push(v); - } - names.push(v); - } - - return { names, selected }; - }) - ); - }; - - const onToggleReplaceFields = (e: React.FormEvent) => { - onChange({ - ...options, - replaceFields: e.currentTarget.checked, - }); - }; - - const onModeChanged = (value: SelectableValue) => { - const mode = value.value ?? CalculateFieldMode.BinaryOperation; - if (mode === CalculateFieldMode.WindowFunctions) { - options.window = options.window ?? defaultWindowOptions; - } - onChange({ - ...options, - mode, - }); - }; - - const onAliasChanged = (evt: ChangeEvent) => { - onChange({ - ...options, - alias: evt.target.value, - }); - }; - - //--------------------------------------------------------- - // Row index - //--------------------------------------------------------- - - const onToggleRowIndexAsPercentile = (e: React.FormEvent) => { - onChange({ - ...options, - index: { - asPercentile: e.currentTarget.checked, - }, - }); - }; - - const renderRowIndex = (options?: IndexOptions) => { - return ( - <> - - - - - ); - }; - - //--------------------------------------------------------- - // Window functions - //--------------------------------------------------------- - - const updateWindowOptions = (v: WindowOptions) => { - const { options, onChange } = props; - onChange({ - ...options, - mode: CalculateFieldMode.WindowFunctions, - window: v, - }); - }; - - const onWindowFieldChange = (v: SelectableValue) => { - const { window } = options; - updateWindowOptions({ - ...window!, - field: v.value!, - }); - }; - - const onWindowSizeChange = (v?: number) => { - const { window } = options; - updateWindowOptions({ - ...window!, - windowSize: v && window?.windowSizeMode === WindowSizeMode.Percentage ? v / 100 : v, - }); - }; - - const onWindowSizeModeChange = (val: string) => { - const { window } = options; - const mode = val as WindowSizeMode; - updateWindowOptions({ - ...window!, - windowSize: window?.windowSize - ? mode === WindowSizeMode.Percentage - ? window!.windowSize! / 100 - : window!.windowSize! * 100 - : undefined, - windowSizeMode: mode, - }); - }; - - const onWindowStatsChange = (stats: string[]) => { - const reducer = stats.length ? (stats[0] as ReducerID) : ReducerID.sum; - - const { window } = options; - updateWindowOptions({ ...window, reducer }); - }; - - const onTypeChange = (val: string) => { - const { window } = options; - updateWindowOptions({ - ...window!, - windowAlignment: val as WindowAlignment, - }); - }; - - const renderWindowFunctions = (options?: WindowOptions) => { - const { names } = state; - options = defaults(options, { reducer: ReducerID.sum }); - const selectOptions = names.map((v) => ({ label: v, value: v })); - const typeOptions = [ - { label: 'Trailing', value: WindowAlignment.Trailing }, - { label: 'Centered', value: WindowAlignment.Centered }, - ]; - const windowSizeModeOptions = [ - { label: 'Percentage', value: WindowSizeMode.Percentage }, - { label: 'Fixed', value: WindowSizeMode.Fixed }, - ]; - - return ( - <> - - - - - ext.id === ReducerID.sum || ext.id === ReducerID.mean} - /> - - - ); - }; - - //--------------------------------------------------------- - // Binary Operator - //--------------------------------------------------------- - - const updateBinaryOptions = (v: BinaryOptions) => { - onChange({ - ...options, - mode: CalculateFieldMode.BinaryOperation, - binary: v, - }); - }; - - const onBinaryLeftChanged = (v: SelectableValue) => { - const { binary } = options; - updateBinaryOptions({ - ...binary!, - left: v.value!, - }); - }; - - const onBinaryRightChanged = (v: SelectableValue) => { - const { binary } = options; - updateBinaryOptions({ - ...binary!, - right: v.value!, - }); - }; - - const onBinaryOperationChanged = (v: SelectableValue) => { - const { binary } = options; - updateBinaryOptions({ - ...binary!, - operator: v.value!, - }); - }; - - const renderBinaryOperation = (options?: BinaryOptions) => { - options = defaults(options, { operator: BinaryOperationID.Add }); - - let foundLeft = !options?.left; - let foundRight = !options?.right; - const names = state.names.map((v) => { - if (v === options?.left) { - foundLeft = true; - } - if (v === options?.right) { - foundRight = true; - } - return { label: v, value: v }; - }); - const leftNames = foundLeft ? names : [...names, { label: options?.left, value: options?.left }]; - const rightNames = foundRight ? names : [...names, { label: options?.right, value: options?.right }]; - - const ops = binaryOperators.list().map((v) => { - return { label: v.binaryOperationID, value: v.binaryOperationID }; - }); - - return ( - <> - - - - - - - - - v.value === mode)} - onChange={onModeChanged} - /> - - {mode === CalculateFieldMode.BinaryOperation && renderBinaryOperation(options.binary)} - {mode === CalculateFieldMode.UnaryOperation && renderUnaryOperation(options.unary)} - {mode === CalculateFieldMode.ReduceRow && renderReduceRow(options.reduce)} - {mode === CalculateFieldMode.CumulativeFunctions && renderCumulativeFunctions(options.cumulative)} - {mode === CalculateFieldMode.WindowFunctions && renderWindowFunctions(options.window)} - {mode === CalculateFieldMode.Index && renderRowIndex(options.index)} - - - - - - - - ); -}; - -export const calculateFieldTransformRegistryItem: TransformerRegistryItem = { - id: DataTransformerID.calculateField, - editor: CalculateFieldTransformerEditor, - transformation: standardTransformers.calculateFieldTransformer, - name: standardTransformers.calculateFieldTransformer.name, - description: 'Use the row values to calculate a new field.', - categories: new Set([TransformerCategory.CalculateNewFields]), - help: getTransformationContent(DataTransformerID.calculateField).helperDocs, -}; diff --git a/public/app/features/transformers/editors/CalculateFieldTransformerEditor/BinaryOperationOptionsEditor.tsx b/public/app/features/transformers/editors/CalculateFieldTransformerEditor/BinaryOperationOptionsEditor.tsx new file mode 100644 index 00000000000..842cf0a670a --- /dev/null +++ b/public/app/features/transformers/editors/CalculateFieldTransformerEditor/BinaryOperationOptionsEditor.tsx @@ -0,0 +1,102 @@ +import React from 'react'; + +import { BinaryOperationID, binaryOperators, SelectableValue } from '@grafana/data'; +import { + BinaryOptions, + CalculateFieldMode, + CalculateFieldTransformerOptions, +} from '@grafana/data/src/transformations/transformers/calculateField'; +import { InlineField, InlineFieldRow, Select } from '@grafana/ui'; + +import { LABEL_WIDTH } from './constants'; + +export const BinaryOperationOptionsEditor = (props: { + options: CalculateFieldTransformerOptions; + onChange: (options: CalculateFieldTransformerOptions) => void; + names: string[]; +}) => { + const { options, onChange } = props; + const { binary } = options; + + let foundLeft = !binary?.left; + let foundRight = !binary?.right; + const names = props.names.map((v) => { + if (v === binary?.left) { + foundLeft = true; + } + if (v === binary?.right) { + foundRight = true; + } + return { label: v, value: v }; + }); + const leftNames = foundLeft ? names : [...names, { label: binary?.left, value: binary?.left }]; + const rightNames = foundRight ? names : [...names, { label: binary?.right, value: binary?.right }]; + + const ops = binaryOperators.list().map((v) => { + return { label: v.binaryOperationID, value: v.binaryOperationID }; + }); + + const updateBinaryOptions = (v: BinaryOptions) => { + onChange({ + ...options, + mode: CalculateFieldMode.BinaryOperation, + binary: v, + }); + }; + + const onBinaryLeftChanged = (v: SelectableValue) => { + updateBinaryOptions({ + ...binary!, + left: v.value!, + }); + }; + + const onBinaryRightChanged = (v: SelectableValue) => { + updateBinaryOptions({ + ...binary!, + right: v.value!, + }); + }; + + const onBinaryOperationChanged = (v: SelectableValue) => { + updateBinaryOptions({ + ...binary!, + operator: v.value!, + }); + }; + + return ( + <> + + + + + + v.value === mode)} + onChange={onModeChanged} + /> + + {mode === CalculateFieldMode.BinaryOperation && ( + + )} + {mode === CalculateFieldMode.UnaryOperation && ( + + )} + {mode === CalculateFieldMode.ReduceRow && ( + + )} + {mode === CalculateFieldMode.CumulativeFunctions && ( + + )} + {mode === CalculateFieldMode.WindowFunctions && ( + + )} + {mode === CalculateFieldMode.Index && ( + + )} + + + + + + + + ); +}; + +export const calculateFieldTransformRegistryItem: TransformerRegistryItem = { + id: DataTransformerID.calculateField, + editor: CalculateFieldTransformerEditor, + transformation: standardTransformers.calculateFieldTransformer, + name: standardTransformers.calculateFieldTransformer.name, + description: 'Use the row values to calculate a new field.', + categories: new Set([TransformerCategory.CalculateNewFields]), + help: getTransformationContent(DataTransformerID.calculateField).helperDocs, +}; diff --git a/public/app/features/transformers/editors/CalculateFieldTransformerEditor/CumulativeOptionsEditor.tsx b/public/app/features/transformers/editors/CalculateFieldTransformerEditor/CumulativeOptionsEditor.tsx new file mode 100644 index 00000000000..406a8745872 --- /dev/null +++ b/public/app/features/transformers/editors/CalculateFieldTransformerEditor/CumulativeOptionsEditor.tsx @@ -0,0 +1,66 @@ +import React from 'react'; + +import { ReducerID, SelectableValue } from '@grafana/data'; +import { + CalculateFieldMode, + CalculateFieldTransformerOptions, + CumulativeOptions, +} from '@grafana/data/src/transformations/transformers/calculateField'; +import { InlineField, Select, StatsPicker } from '@grafana/ui'; + +import { LABEL_WIDTH } from './constants'; + +export const CumulativeOptionsEditor = (props: { + options: CalculateFieldTransformerOptions; + names: string[]; + onChange: (options: CalculateFieldTransformerOptions) => void; +}) => { + const { names, onChange, options } = props; + const { cumulative } = options; + const selectOptions = names.map((v) => ({ label: v, value: v })); + + const onCumulativeStatsChange = (stats: string[]) => { + const reducer = stats.length ? (stats[0] as ReducerID) : ReducerID.sum; + + updateCumulativeOptions({ ...cumulative, reducer }); + }; + + const updateCumulativeOptions = (v: CumulativeOptions) => { + onChange({ + ...options, + mode: CalculateFieldMode.CumulativeFunctions, + cumulative: v, + }); + }; + + const onCumulativeFieldChange = (v: SelectableValue) => { + updateCumulativeOptions({ + ...cumulative!, + field: v.value!, + }); + }; + + return ( + <> + + + + + + + + + ext.id === ReducerID.mean || ext.id === ReducerID.variance || ext.id === ReducerID.stdDev + } + /> + + + + + + + + + + + + ); +}; diff --git a/public/app/features/transformers/editors/CalculateFieldTransformerEditor/constants.ts b/public/app/features/transformers/editors/CalculateFieldTransformerEditor/constants.ts new file mode 100644 index 00000000000..49e3214f010 --- /dev/null +++ b/public/app/features/transformers/editors/CalculateFieldTransformerEditor/constants.ts @@ -0,0 +1 @@ +export const LABEL_WIDTH = 16; diff --git a/public/app/features/transformers/editors/CalculateFieldTransformerEditor/index.ts b/public/app/features/transformers/editors/CalculateFieldTransformerEditor/index.ts new file mode 100644 index 00000000000..0772bb9cd00 --- /dev/null +++ b/public/app/features/transformers/editors/CalculateFieldTransformerEditor/index.ts @@ -0,0 +1,6 @@ +import { + CalculateFieldTransformerEditor, + calculateFieldTransformRegistryItem, +} from './CalculateFieldTransformerEditor'; + +export { CalculateFieldTransformerEditor, calculateFieldTransformRegistryItem };