From 18fc1a276152346ca95c438fbce1835e7bce17f4 Mon Sep 17 00:00:00 2001 From: Yevhen Shevchenko <90138953+yeshev@users.noreply.github.com> Date: Thu, 18 May 2023 14:14:34 +0300 Subject: [PATCH] fix: having filter removing payload (#2706) --- .../api/queryBuilder/getAggregateAttribute.ts | 12 +- .../src/api/queryBuilder/getAttributeKeys.ts | 12 +- frontend/src/constants/queryBuilder.ts | 20 +- .../src/constants/queryBuilderOperators.ts | 304 ++++++++++++++++++ .../AggregatorFilter/AggregatorFilter.tsx | 86 ++--- .../filters/GroupByFilter/GroupByFilter.tsx | 84 ++--- .../filters/HavingFilter/HavingFilter.tsx | 4 +- .../OperatorsSelect.interfaces.ts | 3 +- .../OperatorsSelect/OperatorsSelect.tsx | 14 +- .../hooks/queryBuilder/useQueryOperations.ts | 5 +- .../newQueryBuilder/getFilterObjectValue.ts | 20 +- .../getOperatorsBySourceAndPanelType.ts | 5 +- frontend/src/providers/QueryBuilder.tsx | 2 +- .../queryBuilder/queryAutocompleteResponse.ts | 3 +- frontend/src/types/common/operations.types.ts | 4 +- 15 files changed, 458 insertions(+), 120 deletions(-) create mode 100644 frontend/src/constants/queryBuilderOperators.ts diff --git a/frontend/src/api/queryBuilder/getAggregateAttribute.ts b/frontend/src/api/queryBuilder/getAggregateAttribute.ts index 79ac5086f7..cbd6740f31 100644 --- a/frontend/src/api/queryBuilder/getAggregateAttribute.ts +++ b/frontend/src/api/queryBuilder/getAggregateAttribute.ts @@ -6,7 +6,11 @@ import createQueryParams from 'lib/createQueryParams'; import { ErrorResponse, SuccessResponse } from 'types/api'; // ** Types import { IGetAggregateAttributePayload } from 'types/api/queryBuilder/getAggregatorAttribute'; -import { IQueryAutocompleteResponse } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { + BaseAutocompleteData, + IQueryAutocompleteResponse, +} from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { v4 as uuid } from 'uuid'; export const getAggregateAttribute = async ({ aggregateOperator, @@ -26,11 +30,15 @@ export const getAggregateAttribute = async ({ })}`, ); + const payload: BaseAutocompleteData[] = + response.data.data.attributeKeys?.map((item) => ({ ...item, id: uuid() })) || + []; + return { statusCode: 200, error: null, message: response.statusText, - payload: response.data.data, + payload: { attributeKeys: payload }, }; } catch (e) { return ErrorResponseHandler(e as AxiosError); diff --git a/frontend/src/api/queryBuilder/getAttributeKeys.ts b/frontend/src/api/queryBuilder/getAttributeKeys.ts index 2272be321d..b47509de0a 100644 --- a/frontend/src/api/queryBuilder/getAttributeKeys.ts +++ b/frontend/src/api/queryBuilder/getAttributeKeys.ts @@ -5,7 +5,11 @@ import createQueryParams from 'lib/createQueryParams'; import { ErrorResponse, SuccessResponse } from 'types/api'; // ** Types import { IGetAttributeKeysPayload } from 'types/api/queryBuilder/getAttributeKeys'; -import { IQueryAutocompleteResponse } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { + BaseAutocompleteData, + IQueryAutocompleteResponse, +} from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { v4 as uuid } from 'uuid'; export const getAggregateKeys = async ({ aggregateOperator, @@ -28,11 +32,15 @@ export const getAggregateKeys = async ({ })}&tagType=${tagType}`, ); + const payload: BaseAutocompleteData[] = + response.data.data.attributeKeys?.map((item) => ({ ...item, id: uuid() })) || + []; + return { statusCode: 200, error: null, message: response.statusText, - payload: response.data.data, + payload: { attributeKeys: payload }, }; } catch (e) { return ErrorResponseHandler(e as AxiosError); diff --git a/frontend/src/constants/queryBuilder.ts b/frontend/src/constants/queryBuilder.ts index 18e423f7dc..525c679681 100644 --- a/frontend/src/constants/queryBuilder.ts +++ b/frontend/src/constants/queryBuilder.ts @@ -10,20 +10,27 @@ import { import { BoolOperators, DataSource, - LogsAggregatorOperator, MetricAggregateOperator, NumberOperators, PanelTypeKeys, QueryAdditionalFilter, ReduceOperators, StringOperators, - TracesAggregatorOperator, } from 'types/common/queryBuilder'; import { SelectOption } from 'types/common/select'; +import { v4 as uuid } from 'uuid'; + +import { + logsAggregateOperatorOptions, + metricAggregateOperatorOptions, + tracesAggregateOperatorOptions, +} from './queryBuilderOperators'; export const MAX_FORMULAS = 20; export const MAX_QUERIES = 26; +export const selectValueDivider = '--'; + export const formulasNames: string[] = Array.from( Array(MAX_FORMULAS), (_, i) => `F${i + 1}`, @@ -37,10 +44,10 @@ export enum QueryBuilderKeys { GET_ATTRIBUTE_KEY = 'GET_ATTRIBUTE_KEY', } -export const mapOfOperators: Record = { - metrics: Object.values(MetricAggregateOperator), - logs: Object.values(LogsAggregatorOperator), - traces: Object.values(TracesAggregatorOperator), +export const mapOfOperators = { + metrics: metricAggregateOperatorOptions, + logs: logsAggregateOperatorOptions, + traces: tracesAggregateOperatorOptions, }; export const mapOfFilters: Record = { @@ -78,6 +85,7 @@ export const initialHavingValues: HavingForm = { }; export const initialAggregateAttribute: IBuilderQuery['aggregateAttribute'] = { + id: uuid(), dataType: null, key: '', isColumn: null, diff --git a/frontend/src/constants/queryBuilderOperators.ts b/frontend/src/constants/queryBuilderOperators.ts new file mode 100644 index 0000000000..7c5cff2b69 --- /dev/null +++ b/frontend/src/constants/queryBuilderOperators.ts @@ -0,0 +1,304 @@ +import { + LogsAggregatorOperator, + MetricAggregateOperator, + TracesAggregatorOperator, +} from 'types/common/queryBuilder'; +import { SelectOption } from 'types/common/select'; + +export const metricAggregateOperatorOptions: SelectOption[] = [ + { + value: MetricAggregateOperator.NOOP, + label: 'NOOP', + }, + { + value: MetricAggregateOperator.COUNT, + label: 'Count', + }, + { + value: MetricAggregateOperator.COUNT_DISTINCT, + // eslint-disable-next-line sonarjs/no-duplicate-string + label: 'Count Distinct', + }, + { + value: MetricAggregateOperator.SUM, + label: 'Sum', + }, + { + value: MetricAggregateOperator.AVG, + label: 'Avg', + }, + { + value: MetricAggregateOperator.MAX, + label: 'Max', + }, + { + value: MetricAggregateOperator.MIN, + label: 'Min', + }, + { + value: MetricAggregateOperator.P05, + label: 'P05', + }, + { + value: MetricAggregateOperator.P10, + label: 'P10', + }, + { + value: MetricAggregateOperator.P20, + label: 'P20', + }, + { + value: MetricAggregateOperator.P25, + label: 'P25', + }, + { + value: MetricAggregateOperator.P50, + label: 'P50', + }, + { + value: MetricAggregateOperator.P75, + label: 'P75', + }, + { + value: MetricAggregateOperator.P90, + label: 'P90', + }, + { + value: MetricAggregateOperator.P95, + label: 'P95', + }, + { + value: MetricAggregateOperator.P99, + label: 'P99', + }, + { + value: MetricAggregateOperator.RATE, + label: 'Rate', + }, + { + value: MetricAggregateOperator.SUM_RATE, + label: 'Sum_rate', + }, + { + value: MetricAggregateOperator.AVG_RATE, + label: 'Avg_rate', + }, + { + value: MetricAggregateOperator.MAX_RATE, + label: 'Max_rate', + }, + { + value: MetricAggregateOperator.MIN_RATE, + label: 'Min_rate', + }, + { + value: MetricAggregateOperator.RATE_SUM, + label: 'Rate_sum', + }, + { + value: MetricAggregateOperator.RATE_AVG, + label: 'Rate_avg', + }, + { + value: MetricAggregateOperator.RATE_MIN, + label: 'Rate_min', + }, + { + value: MetricAggregateOperator.RATE_MAX, + label: 'Rate_max', + }, + { + value: MetricAggregateOperator.HIST_QUANTILE_50, + label: 'Hist_quantile_50', + }, + { + value: MetricAggregateOperator.HIST_QUANTILE_75, + label: 'Hist_quantile_75', + }, + { + value: MetricAggregateOperator.HIST_QUANTILE_90, + label: 'Hist_quantile_90', + }, + { + value: MetricAggregateOperator.HIST_QUANTILE_95, + label: 'Hist_quantile_95', + }, + { + value: MetricAggregateOperator.HIST_QUANTILE_99, + label: 'Hist_quantile_99', + }, +]; + +export const tracesAggregateOperatorOptions: SelectOption[] = [ + { + value: TracesAggregatorOperator.NOOP, + label: 'NOOP', + }, + { + value: TracesAggregatorOperator.COUNT, + label: 'Count', + }, + { + value: TracesAggregatorOperator.COUNT_DISTINCT, + label: 'Count Distinct', + }, + { + value: TracesAggregatorOperator.SUM, + label: 'Sum', + }, + { + value: TracesAggregatorOperator.AVG, + label: 'Avg', + }, + { + value: TracesAggregatorOperator.MAX, + label: 'Max', + }, + { + value: TracesAggregatorOperator.MIN, + label: 'Min', + }, + { + value: TracesAggregatorOperator.P05, + label: 'P05', + }, + { + value: TracesAggregatorOperator.P10, + label: 'P10', + }, + { + value: TracesAggregatorOperator.P20, + label: 'P20', + }, + { + value: TracesAggregatorOperator.P25, + label: 'P25', + }, + { + value: TracesAggregatorOperator.P50, + label: 'P50', + }, + { + value: TracesAggregatorOperator.P75, + label: 'P75', + }, + { + value: TracesAggregatorOperator.P90, + label: 'P90', + }, + { + value: TracesAggregatorOperator.P95, + label: 'P95', + }, + { + value: TracesAggregatorOperator.P99, + label: 'P99', + }, + { + value: TracesAggregatorOperator.RATE, + label: 'Rate', + }, + { + value: TracesAggregatorOperator.RATE_SUM, + label: 'Rate_sum', + }, + { + value: TracesAggregatorOperator.RATE_AVG, + label: 'Rate_avg', + }, + { + value: TracesAggregatorOperator.RATE_MIN, + label: 'Rate_min', + }, + { + value: TracesAggregatorOperator.RATE_MAX, + label: 'Rate_max', + }, +]; + +export const logsAggregateOperatorOptions: SelectOption[] = [ + { + value: LogsAggregatorOperator.NOOP, + label: 'NOOP', + }, + { + value: LogsAggregatorOperator.COUNT, + label: 'Count', + }, + { + value: LogsAggregatorOperator.COUNT_DISTINCT, + label: 'Count Distinct', + }, + { + value: LogsAggregatorOperator.SUM, + label: 'Sum', + }, + { + value: LogsAggregatorOperator.AVG, + label: 'Avg', + }, + { + value: LogsAggregatorOperator.MAX, + label: 'Max', + }, + { + value: LogsAggregatorOperator.MIN, + label: 'Min', + }, + { + value: LogsAggregatorOperator.P05, + label: 'P05', + }, + { + value: LogsAggregatorOperator.P10, + label: 'P10', + }, + { + value: LogsAggregatorOperator.P20, + label: 'P20', + }, + { + value: LogsAggregatorOperator.P25, + label: 'P25', + }, + { + value: LogsAggregatorOperator.P50, + label: 'P50', + }, + { + value: LogsAggregatorOperator.P75, + label: 'P75', + }, + { + value: LogsAggregatorOperator.P90, + label: 'P90', + }, + { + value: LogsAggregatorOperator.P95, + label: 'P95', + }, + { + value: LogsAggregatorOperator.P99, + label: 'P99', + }, + { + value: LogsAggregatorOperator.RATE, + label: 'Rate', + }, + { + value: LogsAggregatorOperator.RATE_SUM, + label: 'Rate_sum', + }, + { + value: LogsAggregatorOperator.RATE_AVG, + label: 'Rate_avg', + }, + { + value: LogsAggregatorOperator.RATE_MIN, + label: 'Rate_min', + }, + { + value: LogsAggregatorOperator.RATE_MAX, + label: 'Rate_max', + }, +]; diff --git a/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx index 1dff437914..8c7b5293c1 100644 --- a/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx +++ b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx @@ -2,14 +2,19 @@ import { AutoComplete, Spin } from 'antd'; // ** Api import { getAggregateAttribute } from 'api/queryBuilder/getAggregateAttribute'; -import { initialAggregateAttribute } from 'constants/queryBuilder'; +import { + initialAggregateAttribute, + QueryBuilderKeys, + selectValueDivider, +} from 'constants/queryBuilder'; import { getFilterObjectValue } from 'lib/newQueryBuilder/getFilterObjectValue'; import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix'; -import React, { memo, useMemo, useState } from 'react'; +import React, { memo, useCallback, useMemo, useState } from 'react'; import { useQuery } from 'react-query'; import { DataSource } from 'types/common/queryBuilder'; import { ExtendedSelectOption } from 'types/common/select'; import { transformToUpperCase } from 'utils/transformToUpperCase'; +import { v4 as uuid } from 'uuid'; import { selectStyle } from '../QueryBuilderSearch/config'; // ** Types @@ -19,57 +24,60 @@ export const AggregatorFilter = memo(function AggregatorFilter({ onChange, query, }: AgregatorFilterProps): JSX.Element { - const [searchText, setSearchText] = useState(''); - + const [optionsData, setOptionsData] = useState([]); const { data, isFetching } = useQuery( [ - 'GET_AGGREGATE_ATTRIBUTE', - searchText, + QueryBuilderKeys.GET_AGGREGATE_ATTRIBUTE, + query.aggregateAttribute.key, query.aggregateOperator, query.dataSource, ], async () => getAggregateAttribute({ - searchText, + searchText: query.aggregateAttribute.key, aggregateOperator: query.aggregateOperator, dataSource: query.dataSource, }), - { enabled: !!query.aggregateOperator && !!query.dataSource }, + { + enabled: !!query.aggregateOperator && !!query.dataSource, + onSuccess: (data) => { + const options: ExtendedSelectOption[] = + data?.payload?.attributeKeys?.map((item) => ({ + label: transformStringWithPrefix({ + str: item.key, + prefix: item.type || '', + condition: !item.isColumn, + }), + value: `${transformStringWithPrefix({ + str: item.key, + prefix: item.type || '', + condition: !item.isColumn, + })}${selectValueDivider}${item.id || uuid()}`, + key: item.id || uuid(), + })) || []; + + setOptionsData(options); + }, + }, ); - const handleSearchAttribute = (searchText: string): void => { - const { key } = getFilterObjectValue(searchText); - setSearchText(key); - }; + const handleChangeAttribute = useCallback( + ( + value: string, + option: ExtendedSelectOption | ExtendedSelectOption[], + ): void => { + const currentOption = option as ExtendedSelectOption; - const optionsData: ExtendedSelectOption[] = - data?.payload?.attributeKeys?.map((item) => ({ - label: transformStringWithPrefix({ - str: item.key, - prefix: item.type || '', - condition: !item.isColumn, - }), - value: transformStringWithPrefix({ - str: item.key, - prefix: item.type || '', - condition: !item.isColumn, - }), - key: transformStringWithPrefix({ - str: item.key, - prefix: item.type || '', - condition: !item.isColumn, - }), - })) || []; + const { key } = getFilterObjectValue(value); - const handleChangeAttribute = (value: string): void => { - const { key, isColumn } = getFilterObjectValue(value); - const currentAttributeObj = data?.payload?.attributeKeys?.find( - (item) => item.key === key && isColumn === item.isColumn, - ) || { ...initialAggregateAttribute, key }; + const currentAttributeObj = data?.payload?.attributeKeys?.find( + (item) => currentOption.key === item.id, + ) || { ...initialAggregateAttribute, key }; - setSearchText(''); - onChange(currentAttributeObj); - }; + onChange(currentAttributeObj); + }, + [data, onChange], + ); const value = useMemo( () => @@ -88,12 +96,10 @@ export const AggregatorFilter = memo(function AggregatorFilter({ return ( : null} options={optionsData} value={value} diff --git a/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx b/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx index e7d84aadc0..257bf60fc9 100644 --- a/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx +++ b/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx @@ -1,15 +1,16 @@ import { Select, Spin } from 'antd'; import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys'; // ** Constants -import { QueryBuilderKeys } from 'constants/queryBuilder'; +import { QueryBuilderKeys, selectValueDivider } from 'constants/queryBuilder'; import { getFilterObjectValue } from 'lib/newQueryBuilder/getFilterObjectValue'; // ** Components // ** Helpers import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix'; -import React, { memo, useMemo, useState } from 'react'; +import React, { memo, useState } from 'react'; import { useQuery } from 'react-query'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { ExtendedSelectOption } from 'types/common/select'; +import { v4 as uuid } from 'uuid'; import { selectStyle } from '../QueryBuilderSearch/config'; import { GroupByFilterProps } from './GroupByFilter.interfaces'; @@ -20,6 +21,7 @@ export const GroupByFilter = memo(function GroupByFilter({ disabled, }: GroupByFilterProps): JSX.Element { const [searchText, setSearchText] = useState(''); + const [optionsData, setOptionsData] = useState([]); const [isFocused, setIsFocused] = useState(false); const { data, isFetching } = useQuery( @@ -31,7 +33,38 @@ export const GroupByFilter = memo(function GroupByFilter({ aggregateOperator: query.aggregateOperator, searchText, }), - { enabled: !disabled && isFocused, keepPreviousData: true }, + { + enabled: !disabled && isFocused, + onSuccess: (data) => { + const keys = query.groupBy.reduce((acc, item) => { + acc.push(item.key); + return acc; + }, []); + + const filteredOptions: BaseAutocompleteData[] = + data?.payload?.attributeKeys?.filter( + (attrKey) => !keys.includes(attrKey.key), + ) || []; + + const options: ExtendedSelectOption[] = + filteredOptions.map((item) => ({ + label: transformStringWithPrefix({ + str: item.key, + prefix: item.type || '', + condition: !item.isColumn, + }), + value: `${transformStringWithPrefix({ + str: item.key, + prefix: item.type || '', + condition: !item.isColumn, + })}${selectValueDivider}${item.id || uuid()}`, + key: item.id || uuid(), + title: item.key, + })) || []; + + setOptionsData(options); + }, + }, ); const handleSearchKeys = (searchText: string): void => { @@ -46,51 +79,27 @@ export const GroupByFilter = memo(function GroupByFilter({ setIsFocused(true); }; - const optionsData: ExtendedSelectOption[] = useMemo(() => { - if (data && data.payload && data.payload.attributeKeys) { - return data.payload.attributeKeys.map((item) => ({ - label: transformStringWithPrefix({ - str: item.key, - prefix: item.type || '', - condition: !item.isColumn, - }), - value: transformStringWithPrefix({ - str: item.key, - prefix: item.type || '', - condition: !item.isColumn, - }), - key: transformStringWithPrefix({ - str: item.key, - prefix: item.type || '', - condition: !item.isColumn, - }), - })); - } - - return []; - }, [data]); - const handleChange = (values: ExtendedSelectOption[]): void => { const groupByValues: BaseAutocompleteData[] = values.map((item) => { const responseKeys = data?.payload?.attributeKeys || []; - const { key, isColumn } = getFilterObjectValue(item.value); + const { key } = getFilterObjectValue(item.value); const existGroupResponse = responseKeys.find( - (group) => group.key === key && group.isColumn === isColumn, + (group) => group.id === item.key, ); + if (existGroupResponse) { return existGroupResponse; } - const existGroupQuery = query.groupBy.find( - (group) => group.key === key && group.isColumn === isColumn, - ); + const existGroupQuery = query.groupBy.find((group) => group.id === item.key); if (existGroupQuery) { return existGroupQuery; } return { + id: uuid(), isColumn: null, key, dataType: null, @@ -98,7 +107,6 @@ export const GroupByFilter = memo(function GroupByFilter({ }; }); - setSearchText(''); onChange(groupByValues); }; @@ -108,16 +116,12 @@ export const GroupByFilter = memo(function GroupByFilter({ prefix: item.type || '', condition: !item.isColumn, }), - key: transformStringWithPrefix({ + key: item.id || uuid(), + value: `${transformStringWithPrefix({ str: item.key, prefix: item.type || '', condition: !item.isColumn, - }), - value: transformStringWithPrefix({ - str: item.key, - prefix: item.type || '', - condition: !item.isColumn, - }), + })}${selectValueDivider}${item.id || uuid()}`, })); return ( diff --git a/frontend/src/container/QueryBuilder/filters/HavingFilter/HavingFilter.tsx b/frontend/src/container/QueryBuilder/filters/HavingFilter/HavingFilter.tsx index e114238b28..a21727e8aa 100644 --- a/frontend/src/container/QueryBuilder/filters/HavingFilter/HavingFilter.tsx +++ b/frontend/src/container/QueryBuilder/filters/HavingFilter/HavingFilter.tsx @@ -204,7 +204,9 @@ export function HavingFilter({ const handleDeselect = (value: string): void => { const result = localValues.filter((item) => item !== value); - setLocalValues(result); + const having: Having[] = result.map(transformFromStringToHaving); + onChange(having); + resetChanges(); }; useEffect(() => { diff --git a/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.interfaces.ts b/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.interfaces.ts index f47e813112..1128a4cad1 100644 --- a/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.interfaces.ts +++ b/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.interfaces.ts @@ -1,7 +1,8 @@ import { SelectProps } from 'antd'; +import { SelectOption } from 'types/common/select'; export type OperatorsSelectProps = Omit & { - operators: string[]; + operators: SelectOption[]; onChange: (value: string) => void; value: string; }; diff --git a/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.tsx b/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.tsx index 48f98a2c1c..090178e90f 100644 --- a/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.tsx +++ b/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.tsx @@ -1,10 +1,7 @@ import { Select } from 'antd'; import React, { memo } from 'react'; -// ** Types -import { SelectOption } from 'types/common/select'; -// ** Helpers -import { transformToUpperCase } from 'utils/transformToUpperCase'; +// ** Types import { selectStyle } from '../QueryBuilderSearch/config'; import { OperatorsSelectProps } from './OperatorsSelect.interfaces'; @@ -14,16 +11,9 @@ export const OperatorsSelect = memo(function OperatorsSelect({ onChange, ...props }: OperatorsSelectProps): JSX.Element { - const operatorsOptions: SelectOption[] = operators.map( - (operator) => ({ - label: transformToUpperCase(operator), - value: operator, - }), - ); - return (