mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-27 18:36:08 +00:00
* Fixed Add Partition Setting in UI for Profiler #8624 * addressing comments
This commit is contained in:
parent
4d15696d71
commit
9bd0dbb67d
@ -12,14 +12,23 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import { Button, InputNumber, Modal, Select, Slider, TreeSelect } from 'antd';
|
import {
|
||||||
|
Button,
|
||||||
|
InputNumber,
|
||||||
|
Modal,
|
||||||
|
Select,
|
||||||
|
Slider,
|
||||||
|
Space,
|
||||||
|
Switch,
|
||||||
|
TreeSelect,
|
||||||
|
} from 'antd';
|
||||||
import Form from 'antd/lib/form';
|
import Form from 'antd/lib/form';
|
||||||
import { List } from 'antd/lib/form/Form';
|
import { List } from 'antd/lib/form/Form';
|
||||||
import { Col, Row } from 'antd/lib/grid';
|
import { Col, Row } from 'antd/lib/grid';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import 'codemirror/addon/fold/foldgutter.css';
|
import 'codemirror/addon/fold/foldgutter.css';
|
||||||
import { isEmpty, isEqual, isUndefined, startCase } from 'lodash';
|
import { isEmpty, isEqual, isUndefined, omit, startCase } from 'lodash';
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import { Controlled as CodeMirror } from 'react-codemirror2';
|
import { Controlled as CodeMirror } from 'react-codemirror2';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -30,10 +39,14 @@ import {
|
|||||||
import {
|
import {
|
||||||
codeMirrorOption,
|
codeMirrorOption,
|
||||||
DEFAULT_INCLUDE_PROFILE,
|
DEFAULT_INCLUDE_PROFILE,
|
||||||
|
INTERVAL_TYPE_OPTIONS,
|
||||||
|
INTERVAL_UNIT_OPTIONS,
|
||||||
PROFILER_METRIC,
|
PROFILER_METRIC,
|
||||||
|
SUPPORTED_PARTITION_TYPE,
|
||||||
} from '../../../constants/profiler.constant';
|
} from '../../../constants/profiler.constant';
|
||||||
import {
|
import {
|
||||||
ColumnProfilerConfig,
|
ColumnProfilerConfig,
|
||||||
|
PartitionProfilerConfig,
|
||||||
TableProfilerConfig,
|
TableProfilerConfig,
|
||||||
} from '../../../generated/entity/data/table';
|
} from '../../../generated/entity/data/table';
|
||||||
import jsonData from '../../../jsons/en';
|
import jsonData from '../../../jsons/en';
|
||||||
@ -59,6 +72,9 @@ const ProfilerSettingsModal: React.FC<ProfilerSettingsModalProps> = ({
|
|||||||
);
|
);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const [enablePartition, setEnablePartition] = useState(false);
|
||||||
|
const [partitionData, setPartitionData] = useState<PartitionProfilerConfig>();
|
||||||
|
|
||||||
const selectOptions = useMemo(() => {
|
const selectOptions = useMemo(() => {
|
||||||
return columns.map(({ name }) => ({
|
return columns.map(({ name }) => ({
|
||||||
label: name,
|
label: name,
|
||||||
@ -82,8 +98,30 @@ const ProfilerSettingsModal: React.FC<ProfilerSettingsModalProps> = ({
|
|||||||
return metricsOptions;
|
return metricsOptions;
|
||||||
}, [columns]);
|
}, [columns]);
|
||||||
|
|
||||||
|
const { partitionColumnOptions, isPartitionDisabled } = useMemo(() => {
|
||||||
|
const partitionColumnOptions = columns.reduce((result, column) => {
|
||||||
|
if (SUPPORTED_PARTITION_TYPE.includes(column.dataType)) {
|
||||||
|
return [
|
||||||
|
...result,
|
||||||
|
{
|
||||||
|
value: column.name,
|
||||||
|
label: column.name,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}, [] as { value: string; label: string }[]);
|
||||||
|
const isPartitionDisabled = partitionColumnOptions.length === 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
partitionColumnOptions,
|
||||||
|
isPartitionDisabled,
|
||||||
|
};
|
||||||
|
}, [columns]);
|
||||||
|
|
||||||
const updateInitialConfig = (tableProfilerConfig: TableProfilerConfig) => {
|
const updateInitialConfig = (tableProfilerConfig: TableProfilerConfig) => {
|
||||||
const { includeColumns } = tableProfilerConfig;
|
const { includeColumns, partitioning } = tableProfilerConfig;
|
||||||
setSqlQuery(tableProfilerConfig.profileQuery || '');
|
setSqlQuery(tableProfilerConfig.profileQuery || '');
|
||||||
setProfileSample(tableProfilerConfig.profileSample || 100);
|
setProfileSample(tableProfilerConfig.profileSample || 100);
|
||||||
setExcludeCol(tableProfilerConfig.excludeColumns || []);
|
setExcludeCol(tableProfilerConfig.excludeColumns || []);
|
||||||
@ -101,6 +139,10 @@ const ProfilerSettingsModal: React.FC<ProfilerSettingsModalProps> = ({
|
|||||||
form.setFieldsValue({ includeColumns: includeColValue });
|
form.setFieldsValue({ includeColumns: includeColValue });
|
||||||
setIncludeCol(includeColValue);
|
setIncludeCol(includeColValue);
|
||||||
}
|
}
|
||||||
|
if (partitioning) {
|
||||||
|
setEnablePartition(partitioning.enablePartitioning || false);
|
||||||
|
form.setFieldsValue({ ...partitioning });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchProfileConfig = async () => {
|
const fetchProfileConfig = async () => {
|
||||||
@ -151,8 +193,13 @@ const ProfilerSettingsModal: React.FC<ProfilerSettingsModalProps> = ({
|
|||||||
includeColumns: !isEqual(includeCol, DEFAULT_INCLUDE_PROFILE)
|
includeColumns: !isEqual(includeCol, DEFAULT_INCLUDE_PROFILE)
|
||||||
? getIncludesColumns()
|
? getIncludesColumns()
|
||||||
: undefined,
|
: undefined,
|
||||||
|
partitioning: enablePartition
|
||||||
|
? {
|
||||||
|
...partitionData,
|
||||||
|
enablePartitioning: enablePartition,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await putTableProfileConfig(tableId, profileConfig);
|
const data = await putTableProfileConfig(tableId, profileConfig);
|
||||||
if (data) {
|
if (data) {
|
||||||
@ -186,6 +233,10 @@ const ProfilerSettingsModal: React.FC<ProfilerSettingsModalProps> = ({
|
|||||||
<Modal
|
<Modal
|
||||||
centered
|
centered
|
||||||
destroyOnClose
|
destroyOnClose
|
||||||
|
bodyStyle={{
|
||||||
|
maxHeight: 600,
|
||||||
|
overflowY: 'scroll',
|
||||||
|
}}
|
||||||
cancelButtonProps={{
|
cancelButtonProps={{
|
||||||
type: 'link',
|
type: 'link',
|
||||||
}}
|
}}
|
||||||
@ -193,12 +244,15 @@ const ProfilerSettingsModal: React.FC<ProfilerSettingsModalProps> = ({
|
|||||||
confirmLoading={isLoading}
|
confirmLoading={isLoading}
|
||||||
data-testid="profiler-settings-modal"
|
data-testid="profiler-settings-modal"
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
|
okButtonProps={{
|
||||||
|
form: 'profiler-setting-form',
|
||||||
|
htmlType: 'submit',
|
||||||
|
}}
|
||||||
okText={t('label.save')}
|
okText={t('label.save')}
|
||||||
title={t('label.settings')}
|
title={t('label.settings')}
|
||||||
visible={visible}
|
visible={visible}
|
||||||
width={630}
|
width={630}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}>
|
||||||
onOk={handleSave}>
|
|
||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
<Col data-testid="profile-sample-container" span={24}>
|
<Col data-testid="profile-sample-container" span={24}>
|
||||||
<p>{t('label.profile-sample-percentage')}</p>
|
<p>{t('label.profile-sample-percentage')}</p>
|
||||||
@ -271,13 +325,17 @@ const ProfilerSettingsModal: React.FC<ProfilerSettingsModalProps> = ({
|
|||||||
<Form
|
<Form
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
form={form}
|
form={form}
|
||||||
|
id="profiler-setting-form"
|
||||||
initialValues={{
|
initialValues={{
|
||||||
includeColumns: includeCol,
|
includeColumns: includeCol,
|
||||||
|
...data?.partitioning,
|
||||||
}}
|
}}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
name="includeColumnsProfiler"
|
name="includeColumnsProfiler"
|
||||||
|
onFinish={handleSave}
|
||||||
onValuesChange={(_, data) => {
|
onValuesChange={(_, data) => {
|
||||||
setIncludeCol(data.includeColumns);
|
setIncludeCol(data.includeColumns);
|
||||||
|
setPartitionData(omit(data, 'includeColumns'));
|
||||||
}}>
|
}}>
|
||||||
<List name="includeColumns">
|
<List name="includeColumns">
|
||||||
{(fields, { add, remove }) => (
|
{(fields, { add, remove }) => (
|
||||||
@ -296,30 +354,33 @@ const ProfilerSettingsModal: React.FC<ProfilerSettingsModalProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={classNames({
|
className={classNames({
|
||||||
'tw-h-40 tw-overflow-auto': includeCol.length > 1,
|
'tw-max-h-40 tw-overflow-y-auto': includeCol.length > 1,
|
||||||
})}
|
})}
|
||||||
data-testid="include-column-container">
|
data-testid="include-column-container">
|
||||||
{fields.map(({ key, name, ...restField }) => (
|
{fields.map(({ key, name, ...restField }) => (
|
||||||
<div className="tw-flex tw-gap-2 tw-w-full" key={key}>
|
<Row gutter={16} key={key}>
|
||||||
|
<Col span={12}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
className="tw-w-11/12 tw-mb-4"
|
className="w-full m-b-md"
|
||||||
{...restField}
|
{...restField}
|
||||||
name={[name, 'columnName']}>
|
name={[name, 'columnName']}>
|
||||||
<Select
|
<Select
|
||||||
className="tw-w-full"
|
className="w-full"
|
||||||
data-testid="exclude-column-select"
|
data-testid="exclude-column-select"
|
||||||
options={selectOptions}
|
options={selectOptions}
|
||||||
placeholder={t('label.select-column-include')}
|
placeholder={t('label.select-column-include')}
|
||||||
size="middle"
|
size="middle"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col className="flex" span={12}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
className="tw-w-11/12 tw-mb-4"
|
className="w-full m-b-md"
|
||||||
{...restField}
|
{...restField}
|
||||||
name={[name, 'metrics']}>
|
name={[name, 'metrics']}>
|
||||||
<TreeSelect
|
<TreeSelect
|
||||||
treeCheckable
|
treeCheckable
|
||||||
className="tw-w-full"
|
className="w-full"
|
||||||
maxTagCount={2}
|
maxTagCount={2}
|
||||||
placeholder={t('label.please-select')}
|
placeholder={t('label.please-select')}
|
||||||
showCheckedStrategy="SHOW_PARENT"
|
showCheckedStrategy="SHOW_PARENT"
|
||||||
@ -330,19 +391,145 @@ const ProfilerSettingsModal: React.FC<ProfilerSettingsModalProps> = ({
|
|||||||
icon={
|
icon={
|
||||||
<SVGIcons
|
<SVGIcons
|
||||||
alt={t('label.delete')}
|
alt={t('label.delete')}
|
||||||
className="tw-w-4"
|
className="w-4"
|
||||||
icon={Icons.DELETE}
|
icon={Icons.DELETE}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
type="text"
|
type="text"
|
||||||
onClick={() => remove(name)}
|
onClick={() => remove(name)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Col>
|
||||||
|
</Row>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</List>
|
</List>
|
||||||
|
<Form.Item className="m-b-xs">
|
||||||
|
<Space size={12}>
|
||||||
|
<p>{t('label.enable-partition')}</p>
|
||||||
|
<Switch
|
||||||
|
checked={enablePartition}
|
||||||
|
data-testid="enable-partition-switch"
|
||||||
|
disabled={isPartitionDisabled}
|
||||||
|
onChange={(value) => setEnablePartition(value)}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
className="m-b-0"
|
||||||
|
label={
|
||||||
|
<span className="text-xs">{t('label.column-name')}</span>
|
||||||
|
}
|
||||||
|
labelCol={{
|
||||||
|
style: {
|
||||||
|
paddingBottom: 8,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
name="partitionColumnName"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: isPartitionDisabled || enablePartition,
|
||||||
|
message: t('message.column-name-required'),
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Select
|
||||||
|
allowClear
|
||||||
|
className="w-full"
|
||||||
|
data-testid="column-name"
|
||||||
|
disabled={isPartitionDisabled || !enablePartition}
|
||||||
|
options={partitionColumnOptions}
|
||||||
|
placeholder={t('message.select-column-name')}
|
||||||
|
size="middle"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
className="m-b-0"
|
||||||
|
label={
|
||||||
|
<span className="text-xs">{t('label.interval-type')}</span>
|
||||||
|
}
|
||||||
|
labelCol={{
|
||||||
|
style: {
|
||||||
|
paddingBottom: 8,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
name="partitionIntervalType"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: isPartitionDisabled || enablePartition,
|
||||||
|
message: t('message.interval-type-required'),
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Select
|
||||||
|
allowClear
|
||||||
|
className="w-full"
|
||||||
|
data-testid="interval-type"
|
||||||
|
disabled={isPartitionDisabled || !enablePartition}
|
||||||
|
options={INTERVAL_TYPE_OPTIONS}
|
||||||
|
placeholder={t('message.select-type-required')}
|
||||||
|
size="middle"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
className="m-b-0"
|
||||||
|
label={<span className="text-xs">{t('label.interval')}</span>}
|
||||||
|
labelCol={{
|
||||||
|
style: {
|
||||||
|
paddingBottom: 8,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
name="partitionInterval"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: isPartitionDisabled || enablePartition,
|
||||||
|
message: t('message.interval-required'),
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<InputNumber
|
||||||
|
className="w-full"
|
||||||
|
data-testid="interval-required"
|
||||||
|
disabled={isPartitionDisabled || !enablePartition}
|
||||||
|
placeholder={t('message.enter-interval')}
|
||||||
|
size="middle"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
className="m-b-0"
|
||||||
|
label={
|
||||||
|
<span className="text-xs">{t('label.interval-unit')}</span>
|
||||||
|
}
|
||||||
|
labelCol={{
|
||||||
|
style: {
|
||||||
|
paddingBottom: 8,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
name="partitionIntervalUnit"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: isPartitionDisabled || enablePartition,
|
||||||
|
message: t('message.interval-unit-required'),
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Select
|
||||||
|
allowClear
|
||||||
|
className="w-full"
|
||||||
|
data-testid="select-interval-unit"
|
||||||
|
disabled={isPartitionDisabled || !enablePartition}
|
||||||
|
options={INTERVAL_UNIT_OPTIONS}
|
||||||
|
placeholder={t('message.select-interval-unit')}
|
||||||
|
size="middle"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
</Form>
|
</Form>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -13,7 +13,12 @@
|
|||||||
|
|
||||||
import { StepperStepType } from 'Models';
|
import { StepperStepType } from 'Models';
|
||||||
import { CSMode } from '../enums/codemirror.enum';
|
import { CSMode } from '../enums/codemirror.enum';
|
||||||
import { ColumnProfilerConfig } from '../generated/entity/data/table';
|
import {
|
||||||
|
ColumnProfilerConfig,
|
||||||
|
DataType,
|
||||||
|
PartitionIntervalType,
|
||||||
|
PartitionIntervalUnit,
|
||||||
|
} from '../generated/entity/data/table';
|
||||||
import { TestCaseStatus } from '../generated/tests/testCase';
|
import { TestCaseStatus } from '../generated/tests/testCase';
|
||||||
import { JSON_TAB_SIZE } from './constants';
|
import { JSON_TAB_SIZE } from './constants';
|
||||||
|
|
||||||
@ -206,3 +211,23 @@ export const STEPS_FOR_ADD_TEST_CASE: Array<StepperStepType> = [
|
|||||||
{ name: 'Select/Add Test Suite', step: 1 },
|
{ name: 'Select/Add Test Suite', step: 1 },
|
||||||
{ name: 'Configure Test Case', step: 2 },
|
{ name: 'Configure Test Case', step: 2 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const SUPPORTED_PARTITION_TYPE = [
|
||||||
|
DataType.Timestamp,
|
||||||
|
DataType.Date,
|
||||||
|
DataType.Datetime,
|
||||||
|
DataType.Timestampz,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const INTERVAL_TYPE_OPTIONS = Object.values(PartitionIntervalType).map(
|
||||||
|
(value) => ({
|
||||||
|
value,
|
||||||
|
label: value,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
export const INTERVAL_UNIT_OPTIONS = Object.values(PartitionIntervalUnit).map(
|
||||||
|
(value) => ({
|
||||||
|
value,
|
||||||
|
label: value,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
@ -245,7 +245,12 @@
|
|||||||
"total-data-assets": "Total data assets",
|
"total-data-assets": "Total data assets",
|
||||||
"data-assets-with-field": "Data assets with {{field}}",
|
"data-assets-with-field": "Data assets with {{field}}",
|
||||||
"total-data-assets-with-tiers": "Total Data assets with tiers",
|
"total-data-assets-with-tiers": "Total Data assets with tiers",
|
||||||
"most-active-user": "Most active user"
|
"most-active-user": "Most active user",
|
||||||
|
"enable-partition": "Enable Partition",
|
||||||
|
"column-name": "Column Name",
|
||||||
|
"interval-type": "Interval Type",
|
||||||
|
"interval": "Interval",
|
||||||
|
"interval-unit": "Interval Unit"
|
||||||
},
|
},
|
||||||
"message": {
|
"message": {
|
||||||
"service-email-required": "Service account Email is required",
|
"service-email-required": "Service account Email is required",
|
||||||
@ -272,7 +277,15 @@
|
|||||||
"total-entity-insight": "Display the total of data assets by type.",
|
"total-entity-insight": "Display the total of data assets by type.",
|
||||||
"active-users": "Display the number of users active.",
|
"active-users": "Display the number of users active.",
|
||||||
"most-active-users": "Displays the most active users on the platform based on page views.",
|
"most-active-users": "Displays the most active users on the platform based on page views.",
|
||||||
"most-viewed-data-assets": "Displays the most viewed data assets."
|
"most-viewed-data-assets": "Displays the most viewed data assets.",
|
||||||
|
"column-name-required": "Column name is required",
|
||||||
|
"select-column-name": "Select column name",
|
||||||
|
"interval-type-required": "Interval type is required",
|
||||||
|
"select-type-required": "Select interval type",
|
||||||
|
"interval-required": "Interval is required",
|
||||||
|
"enter-interval": "Enter interval",
|
||||||
|
"interval-unit-required": "Interval unit is required",
|
||||||
|
"select-interval-unit": "Select interval unit"
|
||||||
},
|
},
|
||||||
"server": {
|
"server": {
|
||||||
"no-followed-entities": "You have not followed anything yet.",
|
"no-followed-entities": "You have not followed anything yet.",
|
||||||
|
@ -362,6 +362,9 @@
|
|||||||
.p-b-xs {
|
.p-b-xs {
|
||||||
padding-bottom: @padding-xs;
|
padding-bottom: @padding-xs;
|
||||||
}
|
}
|
||||||
|
.p-b-xss {
|
||||||
|
padding-bottom: @padding-xss;
|
||||||
|
}
|
||||||
.p-b-sm {
|
.p-b-sm {
|
||||||
padding-bottom: @padding-sm;
|
padding-bottom: @padding-sm;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user