From a5699344bba6a9465b0291baea12e96c9ea3a37c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergey=20G=D0=BEr=D0=B5I=D0=BEv?= Date: Fri, 27 Feb 2026 17:45:52 +0300 Subject: [PATCH] =?UTF-8?q?[DOP-33475]=20[SyncMaster=20UI=20OSS]=20=D0=94?= =?UTF-8?q?=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82=D1=8C=20=D0=BA=D0=BE=D0=BC?= =?UTF-8?q?=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B=20=D1=81=20SQL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 + src/app/styles/variables.less | 1 + src/entities/transformation/constants.ts | 3 + src/entities/transformation/types.ts | 33 ++++++- .../TransformationForm/TransformationForm.tsx | 22 +++-- .../components/FilterComponent/index.tsx | 9 +- .../components/FilterComponent/types.ts | 4 +- .../components/FilterSql/constants.ts | 7 ++ .../components/FilterSql/index.tsx | 25 ++++++ .../components/FilterSql/styles.module.less | 5 ++ .../components/FilterSql/types.ts | 5 ++ .../TransformationFormItem.tsx | 75 +++++++++++----- .../TransformationFormItem/constants.ts | 2 + .../useGetNestedTypesSelectOptions/index.ts | 5 +- .../TransformationFormItem/styles.module.less | 4 + .../TransformationFormItem/types.ts | 8 +- .../ui/TransformationForm/types.ts | 8 +- .../utils/prepareTransformationForm/index.ts | 2 + .../prepareTransformationRequest/index.ts | 31 +++++-- .../FilterColumnsFormItem.tsx | 4 +- .../FilterColumnsNode/FilterColumnsNode.tsx | 2 +- .../FilterFileFormItem/FilterFileFormItem.tsx | 4 +- .../FilterRowsFormItem/FilterRowsFormItem.tsx | 4 +- .../FilterSqlFormItem/FilterSqlFormItem.tsx | 14 +++ .../components/FilterSqlFormItem/index.ts | 1 + .../FilterSqlNode/FilterSqlNode.tsx | 42 +++++++++ .../components/FilterSqlNode/index.ts | 2 + .../FilterSqlNode/styles.module.less | 3 + .../components/FilterSqlNode/types.ts | 5 ++ .../TransferConnectionsCanvas.tsx | 3 + .../TransferConnectionsCanvas/constants.ts | 20 ++--- .../TransferConnectionsCanvas/types.ts | 6 ++ .../utils/getInitialEdges/index.ts | 5 +- .../utils/getInitialNodes/getInitialNodes.ts | 11 +++ .../utils/getInitialNodes/types.ts | 1 + .../TransferConnectionsDefault/constants.tsx | 6 +- .../TransferConnectionsDefault/types.ts | 2 +- .../hooks/useHandleNodes/index.ts | 88 ++++++++----------- .../TransformButtons/styles.module.less | 4 +- .../useSupportedTransformationTypes/index.ts | 7 +- src/shared/config/i18n/translations/en.json | 2 + src/shared/config/i18n/translations/ru.json | 2 + src/shared/ui/AceSqlEditor/constants.ts | 24 +++++ src/shared/ui/AceSqlEditor/index.tsx | 13 +++ src/shared/ui/AceSqlEditor/types.ts | 4 + src/shared/utils/getCountLines/index.ts | 3 + src/shared/utils/index.ts | 1 + yarn.lock | 46 ++++++++++ 48 files changed, 456 insertions(+), 124 deletions(-) create mode 100644 src/entities/transformation/ui/TransformationForm/components/FilterSql/constants.ts create mode 100644 src/entities/transformation/ui/TransformationForm/components/FilterSql/index.tsx create mode 100644 src/entities/transformation/ui/TransformationForm/components/FilterSql/styles.module.less create mode 100644 src/entities/transformation/ui/TransformationForm/components/FilterSql/types.ts create mode 100644 src/entities/transformation/ui/TransformationForm/components/TransformationFormItem/constants.ts create mode 100644 src/features/transfer/MutateTransferForm/components/FilterSqlFormItem/FilterSqlFormItem.tsx create mode 100644 src/features/transfer/MutateTransferForm/components/FilterSqlFormItem/index.ts create mode 100644 src/features/transfer/MutateTransferForm/components/FilterSqlNode/FilterSqlNode.tsx create mode 100644 src/features/transfer/MutateTransferForm/components/FilterSqlNode/index.ts create mode 100644 src/features/transfer/MutateTransferForm/components/FilterSqlNode/styles.module.less create mode 100644 src/features/transfer/MutateTransferForm/components/FilterSqlNode/types.ts create mode 100644 src/shared/ui/AceSqlEditor/constants.ts create mode 100644 src/shared/ui/AceSqlEditor/index.tsx create mode 100644 src/shared/ui/AceSqlEditor/types.ts create mode 100644 src/shared/utils/getCountLines/index.ts diff --git a/package.json b/package.json index 0bef214d..0a483e0b 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@tanstack/react-query": "5.90.21", "@tanstack/react-query-devtools": "5.91.3", "@xyflow/react": "12.10.1", + "ace-builds": "1.43.6", "antd": "4.24.16", "axios": "1.13.5", "clsx": "2.1.1", @@ -32,6 +33,7 @@ "i18next": "24.2.3", "rc-picker": "4.11.3", "react": "18.3.1", + "react-ace": "14.0.1", "react-dom": "18.3.1", "react-error-boundary": "4.1.2", "react-i18next": "15.7.4", diff --git a/src/app/styles/variables.less b/src/app/styles/variables.less index f0ad101e..d1596a24 100644 --- a/src/app/styles/variables.less +++ b/src/app/styles/variables.less @@ -18,6 +18,7 @@ @typography-title-margin-bottom: 0; @input-bg: #f1f3f5; +@input-border: 1px solid #d9d9d9; @select-background: #f1f3f5; @picker-bg: #f1f3f5; @tooltip-bg: #000000; diff --git a/src/entities/transformation/constants.ts b/src/entities/transformation/constants.ts index 272f5db8..4b09662f 100644 --- a/src/entities/transformation/constants.ts +++ b/src/entities/transformation/constants.ts @@ -21,6 +21,7 @@ export const TRANSFORMATIONS_FORM_DEFAULT_VALUE: TransformationsForm = { [TransformationType.FILTER_FILE]: [], [TransformationType.FILTER_ROWS]: [], [TransformationType.FILTER_COLUMNS]: [], + [TransformationType.FILTER_SQL]: [], }; export const TRANSFORMATIONS_REQUEST_DEFAULT_VALUE: Transformations = [ @@ -71,6 +72,8 @@ const FILE_TRANSFORMATION_TYPES = Object.values(TransformationType); const DB_TRANSFORMATION_TYPES = FILE_TRANSFORMATION_TYPES.filter((type) => type !== TransformationType.FILTER_FILE); +export const DEFAULT_TRANSFORMATION_TYPES = DB_TRANSFORMATION_TYPES; + export const CONNECTION_TYPE_SUPPORT_TRANSFORMATION_TYPES: Record = { [ConnectionType.CLICKHOUSE]: DB_TRANSFORMATION_TYPES, [ConnectionType.FTP]: FILE_TRANSFORMATION_TYPES, diff --git a/src/entities/transformation/types.ts b/src/entities/transformation/types.ts index 2e81e09e..748a097f 100644 --- a/src/entities/transformation/types.ts +++ b/src/entities/transformation/types.ts @@ -4,6 +4,7 @@ export enum TransformationType { FILTER_ROWS = 'dataframe_rows_filter', FILTER_COLUMNS = 'dataframe_columns_filter', FILTER_FILE = 'file_metadata_filter', + FILTER_SQL = 'sql', } export enum TransformationFilterRowsType { @@ -107,15 +108,43 @@ export interface TransformationFilterFileForm { filters: Array; } -export type Transformations = Array; +export enum TransformationFilterSqlDialect { + SPARK = 'spark', +} + +export type TransformationFilterSql = { + type: TransformationType.FILTER_SQL; + dialect: TransformationFilterSqlDialect; + query: string; +}; + +export type Transformations = Array< + TransformationFilterRows | TransformationFilterColumns | TransformationFilterFile | TransformationFilterSql +>; + +export interface TransformationFilterSqlSparkItemForm { + dialect: TransformationFilterSqlDialect; + query: string; +} + +export type TransformationFormFilterSql = { + type: TransformationType.FILTER_SQL; + filters: Array; +}; export interface TransformationsForm { [TransformationType.FILTER_FILE]?: TransformationFilterFileForm['filters']; [TransformationType.FILTER_ROWS]?: TransformationFilterRows['filters']; [TransformationType.FILTER_COLUMNS]?: TransformationFilterColumns['filters']; + [TransformationType.FILTER_SQL]?: TransformationFormFilterSql['filters']; } -export type TransformationsFormNestedType = +export type TransformationsFormWithNestedType = + | TransformationType.FILTER_FILE + | TransformationType.FILTER_ROWS + | TransformationType.FILTER_COLUMNS; + +export type TransformationsFormNestedType = Required[T][number]['type']; export interface ShowButtonsContextProps { diff --git a/src/entities/transformation/ui/TransformationForm/TransformationForm.tsx b/src/entities/transformation/ui/TransformationForm/TransformationForm.tsx index 0642b9f0..6dd8bf3b 100644 --- a/src/entities/transformation/ui/TransformationForm/TransformationForm.tsx +++ b/src/entities/transformation/ui/TransformationForm/TransformationForm.tsx @@ -1,9 +1,9 @@ import { Button, Form, FormListFieldData } from 'antd'; -import React, { memo, useLayoutEffect } from 'react'; +import { memo, useLayoutEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { useShowButtons } from '../../hooks'; -import { Transformations, TransformationType } from '../../types'; +import { TransformationsForm, TransformationType } from '../../types'; import { TransformationFormItem } from './components'; import { TransformationFormProps } from './types'; @@ -17,20 +17,24 @@ const TransformationFormComponent = ({ const { isDisplayed } = useShowButtons(); const formInstance = Form.useFormInstance(); - const filtersValues: Transformations[number]['filters'] | undefined = formInstance.getFieldValue([ + const filtersValues: TransformationsForm[T] | undefined = formInstance.getFieldValue([ 'transformations', transformationType, ]); + const isFilterEmpty = !filtersValues?.length; /** Add at least one element to array form value here, * because it is inconvenient to check for the presence of a default value of this array, * when forming a request to backend or initial form values */ useLayoutEffect(() => { - const needFillEmpty = !canHaveEmptyRecordsList && !filtersValues?.length; + const needFillEmpty = !canHaveEmptyRecordsList && isFilterEmpty; if (needFillEmpty) { formInstance.setFieldValue(['transformations', transformationType], [{}]); } - }, [formInstance, filtersValues, transformationType, canHaveEmptyRecordsList]); + }, [formInstance, isFilterEmpty, transformationType, canHaveEmptyRecordsList]); + + const isNeedShowAddNew = (fields: FormListFieldData[]) => + isDisplayed && !(transformationType === TransformationType.FILTER_SQL && fields.length); const canRemoveItem = ({ name }: FormListFieldData) => (name || canHaveEmptyRecordsList) && isDisplayed; @@ -47,9 +51,11 @@ const TransformationFormComponent = ({ key={field.key} /> ))} - + {isNeedShowAddNew(fields) && ( + + )} )} diff --git a/src/entities/transformation/ui/TransformationForm/components/FilterComponent/index.tsx b/src/entities/transformation/ui/TransformationForm/components/FilterComponent/index.tsx index 8f24e0c8..a7151704 100644 --- a/src/entities/transformation/ui/TransformationForm/components/FilterComponent/index.tsx +++ b/src/entities/transformation/ui/TransformationForm/components/FilterComponent/index.tsx @@ -1,5 +1,8 @@ -import React from 'react'; -import { TransformationsFormNestedType, TransformationType } from '@entities/transformation'; +import { + TransformationsFormNestedType, + TransformationsFormWithNestedType, + TransformationType, +} from '@entities/transformation'; import { FilterColumnsValue } from '../FilterColumnsValue'; import { FilterFileValue } from '../FilterFileValue'; @@ -7,7 +10,7 @@ import { FilterRowsValue } from '../FilterRowsValue'; import { FilterComponentProps } from './types'; -export const FilterComponent = ({ +export const FilterComponent = ({ transformationType, nestedType, name, diff --git a/src/entities/transformation/ui/TransformationForm/components/FilterComponent/types.ts b/src/entities/transformation/ui/TransformationForm/components/FilterComponent/types.ts index 1e08f86b..5ee599ca 100644 --- a/src/entities/transformation/ui/TransformationForm/components/FilterComponent/types.ts +++ b/src/entities/transformation/ui/TransformationForm/components/FilterComponent/types.ts @@ -1,6 +1,6 @@ -import { TransformationsFormNestedType, TransformationType } from '@entities/transformation'; +import { TransformationsFormNestedType, TransformationsFormWithNestedType } from '@entities/transformation'; -export interface FilterComponentProps { +export interface FilterComponentProps { name: number; transformationType: T; nestedType?: TransformationsFormNestedType; diff --git a/src/entities/transformation/ui/TransformationForm/components/FilterSql/constants.ts b/src/entities/transformation/ui/TransformationForm/components/FilterSql/constants.ts new file mode 100644 index 00000000..4652a7cb --- /dev/null +++ b/src/entities/transformation/ui/TransformationForm/components/FilterSql/constants.ts @@ -0,0 +1,7 @@ +export const LINE_HEIGHT = 1.7; + +export const WIDTH = '100%'; + +export const HEIGHT = '100px'; + +export const FONT_SIZE = 1.5; diff --git a/src/entities/transformation/ui/TransformationForm/components/FilterSql/index.tsx b/src/entities/transformation/ui/TransformationForm/components/FilterSql/index.tsx new file mode 100644 index 00000000..77f61f99 --- /dev/null +++ b/src/entities/transformation/ui/TransformationForm/components/FilterSql/index.tsx @@ -0,0 +1,25 @@ +import { AceSqlEditor } from '@shared/ui/AceSqlEditor'; +import { getCountLines } from '@shared/utils'; + +import { FilterSqlProps } from './types'; +import { FONT_SIZE, LINE_HEIGHT, WIDTH } from './constants'; +import * as classes from './styles.module.less'; + +export const FilterSql = ({ autoHeightMaxLineCount, value, onChange }: FilterSqlProps) => { + /** Calc the number of lines in the SQL expression to set the height of the editor */ + const linesCount = Math.max(1, getCountLines(value as string)); + const heightLinesCount = autoHeightMaxLineCount ? Math.min(linesCount, autoHeightMaxLineCount) : linesCount; + + return ( + + ); +}; diff --git a/src/entities/transformation/ui/TransformationForm/components/FilterSql/styles.module.less b/src/entities/transformation/ui/TransformationForm/components/FilterSql/styles.module.less new file mode 100644 index 00000000..d4f8be73 --- /dev/null +++ b/src/entities/transformation/ui/TransformationForm/components/FilterSql/styles.module.less @@ -0,0 +1,5 @@ +.root { + border: @input-border; + background-color: @input-bg; + border-radius: 8px; +} diff --git a/src/entities/transformation/ui/TransformationForm/components/FilterSql/types.ts b/src/entities/transformation/ui/TransformationForm/components/FilterSql/types.ts new file mode 100644 index 00000000..dc139f00 --- /dev/null +++ b/src/entities/transformation/ui/TransformationForm/components/FilterSql/types.ts @@ -0,0 +1,5 @@ +export interface FilterSqlProps { + autoHeightMaxLineCount: number; + value?: string; + onChange?: (value: string) => void; +} diff --git a/src/entities/transformation/ui/TransformationForm/components/TransformationFormItem/TransformationFormItem.tsx b/src/entities/transformation/ui/TransformationForm/components/TransformationFormItem/TransformationFormItem.tsx index 57c96044..9f0b0c42 100644 --- a/src/entities/transformation/ui/TransformationForm/components/TransformationFormItem/TransformationFormItem.tsx +++ b/src/entities/transformation/ui/TransformationForm/components/TransformationFormItem/TransformationFormItem.tsx @@ -1,31 +1,49 @@ import { Button, Form, Input } from 'antd'; -import React, { useMemo, useState } from 'react'; +import { useMemo, useState } from 'react'; import { DeleteOutlined } from '@ant-design/icons'; import { Select } from '@shared/ui'; import { useTranslation } from 'react-i18next'; import clsx from 'clsx'; -import { TransformationsFormNestedType, TransformationType } from '../../../../types'; +import { + TransformationsFormNestedType, + TransformationsFormWithNestedType, + TransformationType, +} from '../../../../types'; import { FilterComponent } from '../FilterComponent'; +import { FilterSql } from '../FilterSql'; import { TransformationFormItemProps } from './types'; import { useGetNestedTypesSelectOptions } from './hooks'; import * as classes from './styles.module.less'; +import { SQL_AUTO_HEIGHT_MAX_LINE_COUNT } from './constants'; export const TransformationFormItem = ({ name, transformationType, nestedTypeSelectLabel, hasColumnField, + hasNestedTypeSelectField, + hasFilterComponent, + hasSqlField, onRemove, }: TransformationFormItemProps) => { const { t } = useTranslation('transformation'); - const nestedTypesSelectOptions = useGetNestedTypesSelectOptions(transformationType); + const nestedTypesSelectOptions = useGetNestedTypesSelectOptions( + transformationType as TransformationsFormWithNestedType, + ); const formInstance = Form.useFormInstance(); - const initialType: TransformationsFormNestedType | undefined = useMemo(() => { - return formInstance.getFieldValue(['transformations', transformationType, name, 'type']); - }, [formInstance, name, transformationType]); + const initialType = useMemo(() => { + if (!hasNestedTypeSelectField) return undefined; + + return formInstance.getFieldValue([ + 'transformations', + transformationType as TransformationsFormWithNestedType, + name, + 'type', + ]) as TransformationsFormNestedType; + }, [formInstance, hasNestedTypeSelectField, name, transformationType]); /** Use custom type state, because Form.useWatch doesn't support dynamic fieldname like in Form.List */ const [type, setType] = useState(() => initialType); @@ -41,23 +59,36 @@ export const TransformationFormItem = ({ )} - - + + )} + {hasNestedTypeSelectField && ( + - - + )} + {hasSqlField && ( + + + + )} {onRemove && (