From 4b260f173668d6bb3e44b72e5fd2670b96594268 Mon Sep 17 00:00:00 2001 From: Shailesh Parmar Date: Tue, 28 Mar 2023 12:13:16 +0530 Subject: [PATCH] fixes 10357: Update Partitionning Setting Flow for Profiler (#10743) * initial commit for #10357 * added form based on partition condition * localization sync * added unit test * updated sql editor with common component * updated form based on switch * addressing comment * added form type and provided to form instance * added default value for partitionValues in initialValue field --- .../components/ParameterForm.tsx | 6 +- .../Component/ProfilerSettingsModal.test.tsx | 57 ++- .../Component/ProfilerSettingsModal.tsx | 419 ++++++++++++------ .../TableProfiler/TableProfiler.interface.ts | 10 + .../ui/src/constants/profiler.constant.ts | 20 +- .../ui/src/locale/languages/en-us.json | 2 + .../ui/src/locale/languages/fr-fr.json | 2 + .../ui/src/locale/languages/ja-jp.json | 2 + .../ui/src/locale/languages/zh-cn.json | 2 + 9 files changed, 382 insertions(+), 138 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AddDataQualityTest/components/ParameterForm.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AddDataQualityTest/components/ParameterForm.tsx index a4d00dd922b..045ddb15856 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AddDataQualityTest/components/ParameterForm.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AddDataQualityTest/components/ParameterForm.tsx @@ -14,7 +14,7 @@ import { PlusOutlined } from '@ant-design/icons'; import { Button, Form, Input, InputNumber, Select, Switch } from 'antd'; import 'codemirror/addon/fold/foldgutter.css'; -import { SUPPORTED_PARTITION_TYPE } from 'constants/profiler.constant'; +import { SUPPORTED_PARTITION_TYPE_FOR_DATE_TIME } from 'constants/profiler.constant'; import { isUndefined } from 'lodash'; import React from 'react'; import { useTranslation } from 'react-i18next'; @@ -46,7 +46,9 @@ const ParameterForm: React.FC = ({ definition, table }) => { ) { const partitionColumnOptions = table.columns.reduce( (result, column) => { - if (SUPPORTED_PARTITION_TYPE.includes(column.dataType)) { + if ( + SUPPORTED_PARTITION_TYPE_FOR_DATE_TIME.includes(column.dataType) + ) { return [ ...result, { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TableProfiler/Component/ProfilerSettingsModal.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/TableProfiler/Component/ProfilerSettingsModal.test.tsx index 1adb8c96491..025436cc54e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/TableProfiler/Component/ProfilerSettingsModal.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/TableProfiler/Component/ProfilerSettingsModal.test.tsx @@ -11,21 +11,18 @@ * limitations under the License. */ -import { cleanup, render, screen } from '@testing-library/react'; +import { + act, + cleanup, + fireEvent, + render, + screen, +} from '@testing-library/react'; import React from 'react'; import { MOCK_TABLE } from '../../../mocks/TableData.mock'; import { ProfilerSettingsModalProps } from '../TableProfiler.interface'; import ProfilerSettingsModal from './ProfilerSettingsModal'; -jest.mock('antd/lib/grid', () => ({ - Row: jest.fn().mockImplementation(({ children }) =>
{children}
), - Col: jest - .fn() - .mockImplementation(({ children, ...props }) => ( -
{children}
- )), -})); - jest.mock('rest/tableAPI', () => ({ getTableProfilerConfig: jest .fn() @@ -55,11 +52,51 @@ describe('Test ProfilerSettingsModal component', () => { const sqlEditor = await screen.findByTestId('sql-editor-container'); const includeSelect = await screen.findByTestId('include-column-container'); const excludeSelect = await screen.findByTestId('exclude-column-container'); + const partitionSwitch = await screen.findByTestId( + 'enable-partition-switch' + ); + const intervalType = await screen.findByTestId('interval-type'); + const columnName = await screen.findByTestId('column-name'); expect(modal).toBeInTheDocument(); expect(sampleContainer).toBeInTheDocument(); expect(sqlEditor).toBeInTheDocument(); expect(includeSelect).toBeInTheDocument(); expect(excludeSelect).toBeInTheDocument(); + expect(partitionSwitch).toBeInTheDocument(); + expect(intervalType).toBeInTheDocument(); + expect(columnName).toBeInTheDocument(); + }); + + it('Interval Type and Column Name field should be disabled, when partition switch is off', async () => { + render(); + const partitionSwitch = await screen.findByTestId( + 'enable-partition-switch' + ); + const intervalType = await screen.findByTestId('interval-type'); + const columnName = await screen.findByTestId('column-name'); + + expect(partitionSwitch).toHaveAttribute('aria-checked', 'false'); + expect(intervalType).toHaveClass('ant-select-disabled'); + expect(columnName).toHaveClass('ant-select-disabled'); + }); + + it('Interval Type and Column Name field should be enabled, when partition switch is on', async () => { + render(); + const partitionSwitch = await screen.findByTestId( + 'enable-partition-switch' + ); + const intervalType = await screen.findByTestId('interval-type'); + const columnName = await screen.findByTestId('column-name'); + + expect(partitionSwitch).toHaveAttribute('aria-checked', 'false'); + + await act(async () => { + fireEvent.click(partitionSwitch); + }); + + expect(partitionSwitch).toHaveAttribute('aria-checked', 'true'); + expect(intervalType).not.toHaveClass('ant-select-disabled'); + expect(columnName).not.toHaveClass('ant-select-disabled'); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TableProfiler/Component/ProfilerSettingsModal.tsx b/openmetadata-ui/src/main/resources/ui/src/components/TableProfiler/Component/ProfilerSettingsModal.tsx index 3d0e7dbfdd9..de809abf7ff 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/TableProfiler/Component/ProfilerSettingsModal.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/TableProfiler/Component/ProfilerSettingsModal.tsx @@ -14,6 +14,7 @@ import { PlusOutlined } from '@ant-design/icons'; import { Button, + Input, InputNumber, Modal, Select, @@ -27,7 +28,10 @@ import { Col, Row } from 'antd/lib/grid'; import { AxiosError } from 'axios'; import classNames from 'classnames'; import 'codemirror/addon/fold/foldgutter.css'; -import { isEmpty, isEqual, isUndefined, omit, startCase } from 'lodash'; +import SchemaEditor from 'components/schema-editor/SchemaEditor'; +import { CSMode } from 'enums/codemirror.enum'; +import { PartitionIntervalType } from 'generated/api/data/createTable'; +import { isEmpty, isEqual, isNil, isUndefined, pick, startCase } from 'lodash'; import React, { Reducer, useCallback, @@ -36,17 +40,17 @@ import React, { useReducer, useState, } from 'react'; -import { Controlled as CodeMirror } from 'react-codemirror2'; import { useTranslation } from 'react-i18next'; import { getTableProfilerConfig, putTableProfileConfig } from 'rest/tableAPI'; import { - codeMirrorOption, DEFAULT_INCLUDE_PROFILE, INTERVAL_TYPE_OPTIONS, INTERVAL_UNIT_OPTIONS, PROFILER_METRIC, + PROFILER_MODAL_LABEL_STYLE, PROFILE_SAMPLE_OPTIONS, - SUPPORTED_PARTITION_TYPE, + SUPPORTED_COLUMN_DATA_TYPE_FOR_INTERVAL, + TIME_BASED_PARTITION, } from '../../../constants/profiler.constant'; import { ProfileSampleType, @@ -58,6 +62,7 @@ import SVGIcons, { Icons } from '../../../utils/SvgUtils'; import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils'; import SliderWithInput from '../../SliderWithInput/SliderWithInput'; import { + ProfilerForm, ProfilerSettingModalState, ProfilerSettingsModalProps, } from '../TableProfiler.interface'; @@ -70,7 +75,7 @@ const ProfilerSettingsModal: React.FC = ({ onVisibilityChange, }) => { const { t } = useTranslation(); - const [form] = Form.useForm(); + const [form] = Form.useForm(); const [isLoading, setIsLoading] = useState(false); @@ -121,9 +126,14 @@ const ProfilerSettingsModal: React.FC = ({ return metricsOptions; }, [columns]); - const { partitionColumnOptions, isPartitionDisabled } = useMemo(() => { + const partitionIntervalType = Form.useWatch(['partitionIntervalType'], form); + + const partitionColumnOptions = useMemo(() => { const partitionColumnOptions = columns.reduce((result, column) => { - if (SUPPORTED_PARTITION_TYPE.includes(column.dataType)) { + const filter = partitionIntervalType + ? SUPPORTED_COLUMN_DATA_TYPE_FOR_INTERVAL[partitionIntervalType] + : []; + if (filter.includes(column.dataType)) { return [ ...result, { @@ -135,13 +145,9 @@ const ProfilerSettingsModal: React.FC = ({ return result; }, [] as { value: string; label: string }[]); - const isPartitionDisabled = partitionColumnOptions.length === 0; - return { - partitionColumnOptions, - isPartitionDisabled, - }; - }, [columns]); + return partitionColumnOptions; + }, [columns, partitionIntervalType]); const updateInitialConfig = (tableProfilerConfig: TableProfilerConfig) => { const { @@ -193,7 +199,9 @@ const ProfilerSettingsModal: React.FC = ({ enablePartition: partitioning.enablePartitioning || false, }); - form.setFieldsValue({ ...partitioning }); + form.setFieldsValue({ + ...partitioning, + }); } }; @@ -259,19 +267,23 @@ const ProfilerSettingsModal: React.FC = ({ const profileConfig: TableProfilerConfig = { excludeColumns: excludeCol.length > 0 ? excludeCol : undefined, profileQuery: !isEmpty(sqlQuery) ? sqlQuery : undefined, - ...{ - profileSample: - profileSampleType === ProfileSampleType.Percentage - ? profileSamplePercentage - : profileSampleRows, - profileSampleType: profileSampleType, - }, + profileSample: + profileSampleType === ProfileSampleType.Percentage + ? profileSamplePercentage + : profileSampleRows, + profileSampleType: profileSampleType, includeColumns: !isEqual(includeCol, DEFAULT_INCLUDE_PROFILE) ? getIncludesColumns() : undefined, partitioning: enablePartition ? { ...partitionData, + partitionValues: + partitionIntervalType === PartitionIntervalType.ColumnValue + ? partitionData?.partitionValues?.filter( + (value) => !isEmpty(value) + ) + : undefined, enablePartitioning: enablePartition, } : undefined, @@ -320,19 +332,42 @@ const ProfilerSettingsModal: React.FC = ({ [] ); - const handleCodeMirrorChange = useCallback( - (_Editor, _EditorChange, value) => { - handleStateChange({ - sqlQuery: value, - }); - }, - [] - ); + const handleCodeMirrorChange = useCallback((value) => { + handleStateChange({ + sqlQuery: value, + }); + }, []); + + const handleIncludeColumnsProfiler = useCallback((changedValues, data) => { + const { partitionIntervalType, enablePartitioning } = changedValues; + if (partitionIntervalType || !isNil(enablePartitioning)) { + form.setFieldsValue({ + partitionColumnName: undefined, + partitionIntegerRangeStart: undefined, + partitionIntegerRangeEnd: undefined, + partitionIntervalUnit: undefined, + partitionInterval: undefined, + partitionValues: [''], + }); + } + if (!isNil(enablePartitioning)) { + form.setFieldsValue({ + partitionIntervalType: undefined, + }); + } - const handleIncludeColumnsProfiler = useCallback((_, data) => { handleStateChange({ includeCol: data.includeColumns, - partitionData: omit(data, 'includeColumns'), + partitionData: pick( + data, + 'partitionColumnName', + 'partitionIntegerRangeEnd', + 'partitionIntegerRangeStart', + 'partitionInterval', + 'partitionIntervalType', + 'partitionIntervalUnit', + 'partitionValues' + ), }); }, []); @@ -437,12 +472,15 @@ const ProfilerSettingsModal: React.FC = ({ type: t('label.query'), })}{' '}

- @@ -469,6 +507,7 @@ const ProfilerSettingsModal: React.FC = ({ id="profiler-setting-form" initialValues={{ includeColumns: state?.includeCol, + partitionData: [''], ...state?.data?.partitioning, }} layout="vertical" @@ -546,54 +585,18 @@ const ProfilerSettingsModal: React.FC = ({ )} - - -

{t('label.enable-partition')}

- -
-
- - - {t('label.column-entity', { - entity: t('label.name'), - })} - - } - labelCol={{ - style: { - paddingBottom: 8, - }, - }} - name="partitionColumnName" - rules={[ - { - required: state?.enablePartition, - message: t('message.field-text-is-required', { - fieldText: t('label.column-entity', { - entity: t('label.name'), - }), - }), - }, - ]}> - + {partitionIntervalType && + TIME_BASED_PARTITION.includes(partitionIntervalType) ? ( + <> + + {t('label.interval')} + } + labelCol={PROFILER_MODAL_LABEL_STYLE} + name="partitionInterval" + rules={[ + { + required: state?.enablePartition, + message: t('message.field-text-is-required', { + fieldText: t('label.interval'), + }), + }, + ]}> + + + + + + {t('label.interval-unit')} + + } + labelCol={PROFILER_MODAL_LABEL_STYLE} + name="partitionIntervalUnit" + rules={[ + { + required: state?.enablePartition, + message: t('message.field-text-is-required', { + fieldText: t('label.interval-unit'), + }), + }, + ]}> + + +