Fixed Add Partition Setting in UI for Profiler #8624 (#8673)

* Fixed Add Partition Setting in UI for Profiler #8624

* addressing comments
This commit is contained in:
Shailesh Parmar 2022-11-12 19:23:52 +05:30 committed by GitHub
parent 4d15696d71
commit 9bd0dbb67d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 275 additions and 47 deletions

View File

@ -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,53 +354,182 @@ 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}>
<Form.Item <Col span={12}>
className="tw-w-11/12 tw-mb-4" <Form.Item
{...restField} className="w-full m-b-md"
name={[name, 'columnName']}> {...restField}
<Select name={[name, 'columnName']}>
className="tw-w-full" <Select
data-testid="exclude-column-select" className="w-full"
options={selectOptions} data-testid="exclude-column-select"
placeholder={t('label.select-column-include')} options={selectOptions}
size="middle" placeholder={t('label.select-column-include')}
/> size="middle"
</Form.Item>
<Form.Item
className="tw-w-11/12 tw-mb-4"
{...restField}
name={[name, 'metrics']}>
<TreeSelect
treeCheckable
className="tw-w-full"
maxTagCount={2}
placeholder={t('label.please-select')}
showCheckedStrategy="SHOW_PARENT"
treeData={metricsOptions}
/>
</Form.Item>
<Button
icon={
<SVGIcons
alt={t('label.delete')}
className="tw-w-4"
icon={Icons.DELETE}
/> />
} </Form.Item>
type="text" </Col>
onClick={() => remove(name)} <Col className="flex" span={12}>
/> <Form.Item
</div> className="w-full m-b-md"
{...restField}
name={[name, 'metrics']}>
<TreeSelect
treeCheckable
className="w-full"
maxTagCount={2}
placeholder={t('label.please-select')}
showCheckedStrategy="SHOW_PARENT"
treeData={metricsOptions}
/>
</Form.Item>
<Button
icon={
<SVGIcons
alt={t('label.delete')}
className="w-4"
icon={Icons.DELETE}
/>
}
type="text"
onClick={() => remove(name)}
/>
</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>

View File

@ -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,
})
);

View File

@ -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.",

View File

@ -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;
} }