From 47e8f30ac04e01913dbcea868ba9e5ba02c5b4c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=9C=9D=E4=BF=8A?= Date: Wed, 25 Oct 2023 13:45:35 +0800 Subject: [PATCH] PullRequest: 203 Fixes oceanbase/odc#462 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'fix/dev-4.2.2-462 of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into dev-4.2.2 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/203 Signed-off-by: 晓康 * Fixes oceanbase/odc#462 --- src/common/network/sensitiveColumn.ts | 2 +- .../SensitiveColumn/components/ManualForm.tsx | 1028 +++++++++-------- .../SensitiveColumn/components/index.less | 24 +- .../SensitiveColumn/components/interface.ts | 38 + 4 files changed, 609 insertions(+), 483 deletions(-) create mode 100644 src/page/Project/Sensitive/components/SensitiveColumn/components/interface.ts diff --git a/src/common/network/sensitiveColumn.ts b/src/common/network/sensitiveColumn.ts index e170b2842..552f3fcf5 100644 --- a/src/common/network/sensitiveColumn.ts +++ b/src/common/network/sensitiveColumn.ts @@ -90,7 +90,7 @@ export async function listColumns(projectId: number, database: number[]) { }, }, ); - return result?.data; + return result?.data || []; } export enum ScannResultType { diff --git a/src/page/Project/Sensitive/components/SensitiveColumn/components/ManualForm.tsx b/src/page/Project/Sensitive/components/SensitiveColumn/components/ManualForm.tsx index fa67ecc2e..b0387c379 100644 --- a/src/page/Project/Sensitive/components/SensitiveColumn/components/ManualForm.tsx +++ b/src/page/Project/Sensitive/components/SensitiveColumn/components/ManualForm.tsx @@ -34,7 +34,14 @@ import { message, } from 'antd'; import styles from './index.less'; -import React, { useCallback, useContext, useEffect, useState } from 'react'; +import React, { + forwardRef, + useContext, + useEffect, + useImperativeHandle, + useRef, + useState, +} from 'react'; import SensitiveContext from '../../../SensitiveContext'; import { EColumnType, SelectItemProps } from '../../../interface'; import { useForm } from 'antd/lib/form/Form'; @@ -52,13 +59,12 @@ import timeSvg from '@/svgr/Field-time.svg'; // 同步 OCP 等保三密码强度 import { ESensitiveColumnType } from '@/d.ts/sensitiveColumn'; import ProjectContext from '@/page/Project/ProjectContext'; import { MaskRyleTypeMap } from '@/d.ts'; -const ManualForm: React.FC<{ - modalOpen: boolean; - setModalOpen: React.Dispatch>; - callback: () => void; -}> = ({ modalOpen, setModalOpen, callback }) => { +import _ from 'lodash'; +import { IMaskingAlgorithm } from '@/d.ts/maskingAlgorithm'; +import { TreeNode, SelectNode, DatabaseColumn, ManualFormProps } from './interface'; +const ManualForm: React.FC = ({ modalOpen, setModalOpen, callback }) => { const [formRef] = useForm(); - const [_formRef] = useForm(); + const _formRef = useRef(null); const sensitiveContext = useContext(SensitiveContext); const projectContext = useContext(ProjectContext); const { project } = projectContext; @@ -73,13 +79,7 @@ const ManualForm: React.FC<{ dataSourceName?: string; })[] >([]); - const [loading, setLoading] = useState(false); - const [treeData, setTreeData] = useState([]); const [checkedKeys, setCheckedKeys] = useState([]); - const [data, setData] = useState([]); - const [databaseColumns, setDatabaseColumns] = useState(); - const [allColumns, setAllColumns] = useState(0); - const [checkedColumns, setCheckedColumns] = useState(0); const initDatabases = async () => { const rawData = await listDatabases(projectId); const resData = rawData?.contents?.map((content) => { @@ -107,29 +107,278 @@ const ManualForm: React.FC<{ const handleDatabaseDeselect = async (value: number) => { setDatabaseIds(databaseIds.filter((id) => id !== value)); }; - const getIconByColumnType = (columnType: string) => { - switch (columnType) { - case EColumnType.NUMBER: - return numberSvg; - case EColumnType.VARCHAR2: - case EColumnType.NCHAR: - case EColumnType.CHAR: - case EColumnType.RAW: - case EColumnType.INTERVAL_DAY_TO_SECOND: - case EColumnType.INTERVAL_YEAR_TO_MONTH: - return stringSvg; - case EColumnType.DATE: - case EColumnType.TIMESTAMP: - case EColumnType.TIMESTAMP_WITH_TIME_ZONE: - case EColumnType.TIMESTAMP_WITH_LOCAL_TIME_ZONE: - return timeSvg; - case EColumnType.BLOB: - case EColumnType.CLOB: - return binarySvg; - default: - return stringSvg; - } + const submit = () => { + _formRef.current?.submit(setModalOpen, callback); + }; + const onClose = () => { + return Modal.confirm({ + title: formatMessage({ + id: + 'odc.src.page.Project.Sensitive.components.SensitiveColumn.components.DoYouConfirmThatYou', + }), //'确认要取消手动添加敏感列吗?' + onOk: async () => { + setModalOpen(false); + }, + onCancel: () => {}, + okText: formatMessage({ + id: 'odc.src.page.Project.Sensitive.components.SensitiveColumn.components.Sure', + }), //'确定' + cancelText: formatMessage({ + id: 'odc.src.page.Project.Sensitive.components.SensitiveColumn.components.Cancel', + }), //'取消' + }); }; + useEffect(() => { + initDatabases(); + }, []); + return ( + + + + + + + } + className={styles.manualForm} + > +
+
+ + + +
+ { + formatMessage({ + id: + 'odc.src.page.Project.Sensitive.components.SensitiveColumn.components.CurrentProject', + }) /* 当前项目: */ + } + {project?.name} +
+
+ +
+
+ ); +}; +export default ManualForm; + +const getIconByColumnType = (columnType: string) => { + switch (columnType) { + case EColumnType.NUMBER: + return numberSvg; + case EColumnType.VARCHAR2: + case EColumnType.NCHAR: + case EColumnType.CHAR: + case EColumnType.RAW: + case EColumnType.INTERVAL_DAY_TO_SECOND: + case EColumnType.INTERVAL_YEAR_TO_MONTH: + return stringSvg; + case EColumnType.DATE: + case EColumnType.TIMESTAMP: + case EColumnType.TIMESTAMP_WITH_TIME_ZONE: + case EColumnType.TIMESTAMP_WITH_LOCAL_TIME_ZONE: + return timeSvg; + case EColumnType.BLOB: + case EColumnType.CLOB: + return binarySvg; + default: + return stringSvg; + } +}; +const SelectedSensitiveColumn = forwardRef(function ( + { + projectId, + databaseIds, + maskingAlgorithms, + maskingAlgorithmOptions, + checkedKeys, + setCheckedKeys, + }: { + projectId: number; + databaseIds: number[]; + maskingAlgorithms: IMaskingAlgorithm[]; + maskingAlgorithmOptions: SelectItemProps[]; + checkedKeys: string[]; + setCheckedKeys: React.Dispatch>; + }, + ref, +) { + const [_formRef] = useForm(); + const [loading, setLoading] = useState(false); + const [databaseColumns, setDatabaseColumns] = useState(); + // 左侧Tree展示数据 + const [treeData, setTreeData] = useState([]); + // 左侧Tree展示数据,用于在搜索后还原数据 + const [originTreeData, setOriginTreeData] = useState(); + // 右侧Collapse展示数据 + const [data, setData] = useState([]); + // 右侧Collapse展示备份数据,用于在搜索后还原数据 + const [originData, setOriginData] = useState([]); + // allColumns 左侧敏感列总数、checkedColumns 已勾选的敏感列总数 + const [allColumns, setAllColumns] = useState(0); + const [checkedColumns, setCheckedColumns] = useState(0); + // formData 用于记录右侧表单数据,确保搜索前后已选择的表单值不丢失。 + const [formData, setFormData] = useState< + { + [key in string]: any; + } + >({}); + useImperativeHandle(ref, () => { + return { + submit: async ( + setModalOpen: React.Dispatch>, + callback: () => void, + ) => { + await _formRef.validateFields().catch(); + const rawData = await _formRef.getFieldsValue(); + const _data = []; + Object.keys(rawData).forEach((databaseId) => { + Object.keys(rawData[databaseId]).forEach((type) => { + Object.keys(rawData[databaseId][type]).forEach((tableName) => { + Object.keys(rawData[databaseId][type][tableName]).forEach((columnName) => { + _data.push({ + enabled: true, + columnName, + tableName, + database: { + id: databaseId && parseInt(databaseId), + }, + type, + maskingAlgorithmId: rawData[databaseId][type][tableName][columnName], + }); + }); + }); + }); + }); + const successful = await batchCreateSensitiveColumns(projectId, _data); + if (successful) { + message.success( + formatMessage({ + id: + 'odc.src.page.Project.Sensitive.components.SensitiveColumn.components.SubmittedSuccessfully', + }), //'提交成功' + ); + setModalOpen(false); + callback?.(); + } else { + message.error( + formatMessage({ + id: + 'odc.src.page.Project.Sensitive.components.SensitiveColumn.components.SubmissionFailed', + }), //'提交失败' + ); + } + }, + }; + }); const getColumns = async () => { setLoading(true); const { contents: databaseColumns } = await listColumns(projectId, databaseIds); @@ -204,153 +453,10 @@ const ManualForm: React.FC<{ }); setLoading(false); setTreeData(treeData); + setOriginTreeData(treeData); setAllColumns(allColumns); }; - const WrapCollapse = useCallback( - () => ( - `${d?.databaseId}/${d?.tableTitle}`)} - ghost - className={styles.wrapCollapse} - > - {data?.map((d) => { - return ( - - - - - - {`${d?.databaseTitle}/${d?.tableTitle}`} - - } - key={`${d?.databaseId}/${d?.tableTitle}`} - > - {d?.children?.map((child, index) => { - return ( -
-
-
- - - - -
{child?.title}
-
-
- - - - - { - const target = data.find( - (_data) => - _data?.databaseKey === d.databaseKey && - _data.tableKey === _data.tableKey, - ); - if (target) { - if (target.children.length === 1) { - const newCheckedKeys = checkedKeys.filter( - (checkedKey) => ![d.tableKey, child.key]?.includes(checkedKey), - ); - setCheckedKeys(newCheckedKeys); - } else { - const newCheckedKeys = checkedKeys.filter( - (checkedKey) => ![d.tableKey, child.key]?.includes(checkedKey), - ); - setCheckedKeys(newCheckedKeys); - } - } - }} - /> - -
-
- ); - })} -
- ); - })} -
- ), - [data], - ); + // 插入库表信息到叶节点 const parseTreeData = (treeData) => { let result = []; treeData?.forEach((node) => { @@ -367,7 +473,8 @@ const ManualForm: React.FC<{ }); return result; }; - const parseData = (data) => { + // 解析数据为表单数据 + const parseDataToFields = (data) => { const result = {}; data?.forEach((d) => { d?.children?.forEach((child) => { @@ -395,47 +502,6 @@ const ManualForm: React.FC<{ }); return result; }; - const submit = async () => { - await _formRef.validateFields().catch(); - const rawData = await _formRef.getFieldsValue(); - const _data = []; - Object.keys(rawData).forEach((databaseId) => { - Object.keys(rawData[databaseId]).forEach((type) => { - Object.keys(rawData[databaseId][type]).forEach((tableName) => { - Object.keys(rawData[databaseId][type][tableName]).forEach((columnName) => { - _data.push({ - enabled: true, - columnName, - tableName, - database: { - id: databaseId && parseInt(databaseId), - }, - type, - maskingAlgorithmId: rawData[databaseId][type][tableName][columnName], - }); - }); - }); - }); - }); - const successful = await batchCreateSensitiveColumns(projectId, _data); - if (successful) { - message.success( - formatMessage({ - id: - 'odc.src.page.Project.Sensitive.components.SensitiveColumn.components.SubmittedSuccessfully', - }), //'提交成功' - ); - setModalOpen(false); - callback?.(); - } else { - message.error( - formatMessage({ - id: - 'odc.src.page.Project.Sensitive.components.SensitiveColumn.components.SubmissionFailed', - }), //'提交失败' - ); - } - }; const handleCheckedKeysChange = async () => { const treeData = []; let checkedColumns = 0; @@ -495,15 +561,20 @@ const ManualForm: React.FC<{ setCheckedColumns(checkedColumns); const data = parseTreeData(treeData); setData(data); - await _formRef.setFieldsValue(parseData(data)); + setOriginData(data); + setFormData(parseDataToFields(data)); + await _formRef.setFieldsValue(parseDataToFields(data)); }; const collapseSearch = async function (searchValue: string) { + const values = await _formRef.getFieldsValue(); if (!searchValue) { - handleCheckedKeysChange(); + setData(originData); + await _formRef.setFieldsValue(formData); return; } const newData = []; - data?.forEach((d) => { + const D = _.cloneDeep(originData); + D?.forEach((d) => { if ( d?.databaseTitle?.toLowerCase()?.includes(searchValue?.toLowerCase()) || d?.tableTitle?.toLowerCase()?.includes(searchValue?.toLowerCase()) @@ -518,16 +589,17 @@ const ManualForm: React.FC<{ } } }); + await _formRef.setFieldsValue(_.merge(formData, values)); setData(newData); - await _formRef.setFieldsValue(parseData(data)); }; const treeSearch = async function (searchValue: string) { if (!searchValue) { - await getColumns(); + setTreeData(originTreeData); return; } const newTreeData = []; - treeData?.forEach((td) => { + const copyTreeData = _.cloneDeep(originTreeData); + copyTreeData?.forEach((td) => { if (td?.title?.toLowerCase()?.includes(searchValue?.toLowerCase())) { newTreeData.push(td); } else { @@ -550,262 +622,290 @@ const ManualForm: React.FC<{ }); setTreeData(newTreeData); }; - const onClose = () => { - return Modal.confirm({ - title: formatMessage({ - id: - 'odc.src.page.Project.Sensitive.components.SensitiveColumn.components.DoYouConfirmThatYou', - }), //'确认要取消手动添加敏感列吗?' - onOk: async () => { - setModalOpen(false); - }, - onCancel: () => {}, - okText: formatMessage({ - id: 'odc.src.page.Project.Sensitive.components.SensitiveColumn.components.Sure', - }), //'确定' - cancelText: formatMessage({ - id: 'odc.src.page.Project.Sensitive.components.SensitiveColumn.components.Cancel', - }), //'取消' - }); - }; - useEffect(() => { - initDatabases(); - }, []); useEffect(() => { + setAllColumns(0); + setCheckedColumns(0); + setCheckedKeys([]); + setDatabaseColumns([]); if (databaseIds?.length > 0) { - setCheckedKeys([]); - setAllColumns(0); - setCheckedColumns(0); + setData([]); + setOriginData([]); + setFormData({}); getColumns(); } else { setTreeData([]); - setCheckedKeys([]); - setAllColumns(0); - setCheckedColumns(0); + setOriginTreeData([]); + setData([]); + setOriginData([]); + setFormData({}); } }, [databaseIds]); useEffect(() => { handleCheckedKeysChange(); }, [checkedKeys]); return ( - - - - - - } - className={styles.manualForm} - > -
-
- - - -
- { - formatMessage({ - id: - 'odc.src.page.Project.Sensitive.components.SensitiveColumn.components.CurrentProject', - }) /* 当前项目: */ + 'odc.src.page.Project.Sensitive.components.SensitiveColumn.components.SelectedCheckColumnsItem', + }, + { + checkedColumns: checkedColumns, + }, + ) //`已选 ${checkedColumns} 项` } - {project?.name} -
-
-
-
- { - formatMessage({ - id: - 'odc.src.page.Project.Sensitive.components.SensitiveColumn.components.ChooseSensitiveLandscape', - }) /* 选择敏感列 */ - } -
-
-
- { + setCheckedKeys([]); + }} + placement="left" title={ - formatMessage( - { - id: - 'odc.src.page.Project.Sensitive.components.SensitiveColumn.components.SelectColumnCheckColumns', - }, - { - checkedColumns: checkedColumns, - allColumns: allColumns, - }, - ) //`选择列 (${checkedColumns}/${allColumns})` + formatMessage({ + id: + 'odc.src.page.Project.Sensitive.components.SensitiveColumn.components.AreYouSureYouWant', + }) //'确定要清空已选对象吗?' } - onSearch={treeSearch} > - {loading ? ( -
- -
- ) : databaseIds?.length > 0 && treeData?.length === 0 && !loading ? ( -
- -
- ) : ( - - )} -
-
-
- + { + formatMessage({ id: - 'odc.src.page.Project.Sensitive.components.SensitiveColumn.components.SelectedCheckColumnsItem', - }, - { - checkedColumns: checkedColumns, - }, - ) //`已选 ${checkedColumns} 项` - } - onSearch={collapseSearch} - extra={ - { - setCheckedKeys([]); - }} - placement="left" - title={ - formatMessage({ - id: - 'odc.src.page.Project.Sensitive.components.SensitiveColumn.components.AreYouSureYouWant', - }) //'确定要清空已选对象吗?' - } - > - - { - formatMessage({ - id: - 'odc.src.page.Project.Sensitive.components.SensitiveColumn.components.Empty', - }) /* 清空 */ - } - - - } - disabled - > -
- - -
-
-
+ 'odc.src.page.Project.Sensitive.components.SensitiveColumn.components.Empty', + }) /* 清空 */ + } + + + } + disabled + > + {data?.length > 0 ? ( +
+ `${d?.databaseId}/${d?.tableTitle}`)} + ghost + className={styles.wrapCollapse} + > + {data?.map((d) => { + return ( + + + + + + {`${d?.databaseTitle}/${d?.tableTitle}`} + + } + key={`${d?.databaseId}/${d?.tableTitle}`} + > + {d?.children?.map((child, index) => { + return ( +
+
+
+ + + + +
+ {child?.title} +
+
+
+ + { + setFormData( + _.merge(parseDataToFields(originData), { + [d?.databaseId]: { + [d?.type]: { + [d?.tableTitle]: { + [child?.title]: e, + }, + }, + }, + }), + ); + return e; + }} + > + + + { + const target = data.find( + (_data) => + _data?.databaseKey === d.databaseKey && + _data.tableKey === _data.tableKey, + ); + if (target) { + if (target.children.length === 1) { + const newCheckedKeys = checkedKeys.filter( + (checkedKey) => + ![d.tableKey, child.key]?.includes(checkedKey), + ); + setCheckedKeys(newCheckedKeys); + } else { + const newCheckedKeys = checkedKeys.filter( + (checkedKey) => + ![d.tableKey, child.key]?.includes(checkedKey), + ); + setCheckedKeys(newCheckedKeys); + } + } + }} + /> + +
+
+ ); + })} +
+ ); + })} +
+
+ ) : ( +
+ +
+ )} +
-
+ ); -}; -export default ManualForm; +}); diff --git a/src/page/Project/Sensitive/components/SensitiveColumn/components/index.less b/src/page/Project/Sensitive/components/SensitiveColumn/components/index.less index 933e4ea18..d4b06d913 100644 --- a/src/page/Project/Sensitive/components/SensitiveColumn/components/index.less +++ b/src/page/Project/Sensitive/components/SensitiveColumn/components/index.less @@ -98,18 +98,6 @@ } } -.option123 { - &::after { - content: 'tewswt'; - color: rgb(230, 128, 13); - } - // :global { - // .ant-select-item-option { - // color: red; - // & - // } - // } -} //处理文字内容过长和自定义内容重叠的样式 .lineEllipsis { display: inline-block; @@ -163,12 +151,12 @@ & + & { border-right: 1px; } - } - .centerContainer { - display: flex; - justify-content: center; - align-items: center; - height: 100%; + .centerContainer { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + } } } } diff --git a/src/page/Project/Sensitive/components/SensitiveColumn/components/interface.ts b/src/page/Project/Sensitive/components/SensitiveColumn/components/interface.ts new file mode 100644 index 000000000..d5d54c3c5 --- /dev/null +++ b/src/page/Project/Sensitive/components/SensitiveColumn/components/interface.ts @@ -0,0 +1,38 @@ +import { ESensitiveColumnType } from '@/d.ts/sensitiveColumn'; + +export type TreeNode = { + title: string; + key: string; + databaseId: number; + icon: JSX.Element; + children: TreeNode[]; +}; +export type SelectNode = { + databaseId: number; + databaseKey: string; + databaseTitle: string; + tableKey: string; + tableTitle: string; + type: ESensitiveColumnType; + children: { + title: string; + key: string; + type: ESensitiveColumnType; + columnType: string; + }[]; +}; +export type DatabaseColumn = { + databaseId: number; + databaseName: string; + table2Columns: { + [key in string | number]: any[]; + }; + view2Columns: { + [key in string | number]: any[]; + }; +}; +export interface ManualFormProps { + modalOpen: boolean; + setModalOpen: React.Dispatch>; + callback: () => void; +}