From dc4061b89d99bf604667de6257fcfad3e233049d Mon Sep 17 00:00:00 2001 From: actiontech-zihan Date: Fri, 19 Jun 2026 20:03:51 +0800 Subject: [PATCH 1/2] Add SQL audit rule exception review support --- packages/base/src/scripts/version.ts | 2 +- .../sqle/service/audit_whitelist/index.d.ts | 47 +- .../api/sqle/service/audit_whitelist/index.ts | 75 +- .../shared/lib/api/sqle/service/common.d.ts | 66 + .../lib/api/sqle/service/instance/index.d.ts | 5 + .../lib/api/sqle/service/instance/index.ts | 17 + .../mockApi/sqle/auditWhiteList/data.ts | 25 +- .../mockApi/sqle/auditWhiteList/index.ts | 27 +- .../testUtil/mockApi/sqle/instance/index.ts | 12 + .../ReportDrawer/RuleExceptionDrawer.tsx | 186 ++ .../__snapshots__/index.test.tsx.snap | 1204 ++++++------ .../ReportDrawer/__tests__/index.test.tsx | 82 +- .../src/components/ReportDrawer/index.tsx | 266 ++- .../src/components/ReportDrawer/index.type.ts | 40 +- .../sqle/src/components/ReportDrawer/style.ts | 35 +- .../sqle/src/locale/en-US/operationRecord.ts | 1 + packages/sqle/src/locale/en-US/whitelist.ts | 35 + .../sqle/src/locale/zh-CN/operationRecord.ts | 1 + packages/sqle/src/locale/zh-CN/whitelist.ts | 33 + .../src/page/OperationRecord/List/index.tsx | 176 +- .../src/page/OperationRecord/index.test.tsx | 14 +- .../sqle/src/page/OperationRecord/index.tsx | 27 +- .../Table/AuditResultDrawer.tsx | 54 +- .../Common/AuditResultList/Table/index.tsx | 259 +-- .../AuditResultList/Table/index.type.ts | 35 +- .../Common/AuditResultList/index.tsx | 73 +- .../Common/AuditResultList/index.type.ts | 18 +- .../List/__snapshots__/index.test.tsx.snap | 1712 +++++++++-------- .../sqle/src/page/Whitelist/List/columns.tsx | 142 +- .../src/page/Whitelist/List/index.test.tsx | 142 +- .../sqle/src/page/Whitelist/List/index.tsx | 411 +++- .../__snapshots__/index.test.tsx.snap | 1712 +++++++++-------- .../sqle/src/page/Whitelist/index.test.tsx | 25 +- packages/sqle/src/page/Whitelist/index.tsx | 26 +- 34 files changed, 3974 insertions(+), 3011 deletions(-) create mode 100644 packages/sqle/src/components/ReportDrawer/RuleExceptionDrawer.tsx diff --git a/packages/base/src/scripts/version.ts b/packages/base/src/scripts/version.ts index 6d5baaff06..c3000ac3cf 100644 --- a/packages/base/src/scripts/version.ts +++ b/packages/base/src/scripts/version.ts @@ -1 +1 @@ -export const UI_VERSION = 'sync/data-masking 417b194dd'; +export const UI_VERSION = 'dev/rule-exc a32b5993'; diff --git a/packages/shared/lib/api/sqle/service/audit_whitelist/index.d.ts b/packages/shared/lib/api/sqle/service/audit_whitelist/index.d.ts index 732f283101..6e6e8fc960 100644 --- a/packages/shared/lib/api/sqle/service/audit_whitelist/index.d.ts +++ b/packages/shared/lib/api/sqle/service/audit_whitelist/index.d.ts @@ -2,7 +2,10 @@ import { IGetAuditWhitelistResV1, ICreateAuditWhitelistReqV1, IBaseRes, - IUpdateAuditWhitelistReqV1 + IUpdateAuditWhitelistReqV1, + ISQLRuleExceptionResV1, + ICreateSQLRuleExceptionReqV1, + IGetSQLRuleExceptionResV1 } from '../common.d'; export interface IGetAuditWhitelistV1Params { @@ -42,3 +45,45 @@ export interface IUpdateAuditWhitelistByIdV1Params } export interface IUpdateAuditWhitelistByIdV1Return extends IBaseRes {} + +export interface ICreateSQLRuleExceptionV1Params + extends ICreateSQLRuleExceptionReqV1 { + project_name: string; +} + +export interface ICreateSQLRuleExceptionV1Return extends IBaseRes { + data?: ISQLRuleExceptionResV1; +} + +export interface IGetSQLRuleExceptionV1Params { + project_name: string; + + fuzzy_search_value?: string; + + filter_instance_id?: string; + + filter_rule_name?: string; + + filter_created_by?: string; + + filter_created_time_from?: string; + + filter_created_time_to?: string; + + filter_sql_fingerprint?: string; + + page_index: string; + + page_size: string; +} + +export interface IGetSQLRuleExceptionV1Return + extends IGetSQLRuleExceptionResV1 {} + +export interface IDeleteSQLRuleExceptionV1Params { + project_name: string; + + sql_rule_exception_id: string; +} + +export interface IDeleteSQLRuleExceptionV1Return extends IBaseRes {} diff --git a/packages/shared/lib/api/sqle/service/audit_whitelist/index.ts b/packages/shared/lib/api/sqle/service/audit_whitelist/index.ts index df283dea6f..475ed74d9b 100644 --- a/packages/shared/lib/api/sqle/service/audit_whitelist/index.ts +++ b/packages/shared/lib/api/sqle/service/audit_whitelist/index.ts @@ -14,7 +14,13 @@ import { IDeleteAuditWhitelistByIdV1Params, IDeleteAuditWhitelistByIdV1Return, IUpdateAuditWhitelistByIdV1Params, - IUpdateAuditWhitelistByIdV1Return + IUpdateAuditWhitelistByIdV1Return, + ICreateSQLRuleExceptionV1Params, + ICreateSQLRuleExceptionV1Return, + IDeleteSQLRuleExceptionV1Params, + IDeleteSQLRuleExceptionV1Return, + IGetSQLRuleExceptionV1Params, + IGetSQLRuleExceptionV1Return } from './index.d'; class AuditWhitelistService extends ServiceBase { @@ -48,6 +54,73 @@ class AuditWhitelistService extends ServiceBase { ); } + public createSQLRuleExceptionV1( + params: ICreateSQLRuleExceptionV1Params, + options?: AxiosRequestConfig + ) { + const paramsData = this.cloneDeep(params); + const project_name = paramsData.project_name; + delete paramsData.project_name; + + const config = options || {}; + const headers = config.headers ? config.headers : {}; + config.headers = { + ...headers, + 'Content-Type': 'application/json' + }; + config.transformRequest = [ + (data) => + `{"instance_id":${data.instance_id},"sql_fingerprint":${JSON.stringify( + data.sql_fingerprint ?? '' + )},"rule_name":${JSON.stringify( + data.rule_name ?? '' + )},"rule_desc":${JSON.stringify( + data.rule_desc ?? '' + )},"rule_level":${JSON.stringify( + data.rule_level ?? '' + )},"reason":${JSON.stringify(data.reason ?? '')}}` + ]; + + return this.post( + `/v1/projects/${project_name}/audit_whitelist/rule_exceptions`, + paramsData, + config + ); + } + + public getSQLRuleExceptionV1( + params: IGetSQLRuleExceptionV1Params, + options?: AxiosRequestConfig + ) { + const paramsData = this.cloneDeep(params); + const project_name = paramsData.project_name; + delete paramsData.project_name; + + return this.get( + `/v1/projects/${project_name}/audit_whitelist/rule_exceptions`, + paramsData, + options + ); + } + + public deleteSQLRuleExceptionV1( + params: IDeleteSQLRuleExceptionV1Params, + options?: AxiosRequestConfig + ) { + const paramsData = this.cloneDeep(params); + const project_name = paramsData.project_name; + delete paramsData.project_name; + + const sql_rule_exception_id = paramsData.sql_rule_exception_id; + delete paramsData.sql_rule_exception_id; + + return this.delete( + `/v1/projects/${project_name}/audit_whitelist/rule_exceptions/${sql_rule_exception_id}`, + paramsData, + options + ); + } + public deleteAuditWhitelistByIdV1( params: IDeleteAuditWhitelistByIdV1Params, options?: AxiosRequestConfig diff --git a/packages/shared/lib/api/sqle/service/common.d.ts b/packages/shared/lib/api/sqle/service/common.d.ts index 57804bfc7f..6d4aaea12a 100644 --- a/packages/shared/lib/api/sqle/service/common.d.ts +++ b/packages/shared/lib/api/sqle/service/common.d.ts @@ -1060,6 +1060,66 @@ export interface ICreateAuditWhitelistReqV1 { value?: string; } +export interface ICreateSQLRuleExceptionReqV1 { + instance_id?: string; + + sql_fingerprint?: string; + + rule_name?: string; + + rule_desc?: string; + + rule_level?: string; + + reason?: string; +} + +export interface ISQLRuleExceptionResV1 { + sql_rule_exception_id?: number; + + project_name?: string; + + project_id?: string; + + instance_id?: string; + + instance_name?: string; + + sql_fingerprint?: string; + + rule_name?: string; + + rule_desc?: string; + + rule_level?: string; + + reason?: string; + + created_by?: string; + + created_at?: string; + + hit_count?: number; + + last_match_time?: string; + + matched_count?: number; + + match_info?: string; + + hit_info?: string; +} + +export interface IGetSQLRuleExceptionResV1 { + code?: number; + + data?: ISQLRuleExceptionResV1[]; + + message?: string; + + total_nums?: number; +} + export interface ICreateBlacklistReqV1 { content?: string; @@ -4619,6 +4679,12 @@ export interface IAuditTaskSQLResV2 { audit_result?: IAuditResult[]; + skipped_audit_result?: (IAuditResult & Partial)[]; + + sql_fingerprint?: string; + + audit_fingerprint?: string; + audit_status?: string; backup_result?: string; diff --git a/packages/shared/lib/api/sqle/service/instance/index.d.ts b/packages/shared/lib/api/sqle/service/instance/index.d.ts index ac67ae45eb..0735b07839 100644 --- a/packages/shared/lib/api/sqle/service/instance/index.d.ts +++ b/packages/shared/lib/api/sqle/service/instance/index.d.ts @@ -42,6 +42,11 @@ export interface IGetInstanceTipListV1Params { export interface IGetInstanceTipListV1Return extends IGetInstanceTipsResV1 {} +export interface IGetInstanceTipListV2Params + extends IGetInstanceTipListV1Params {} + +export interface IGetInstanceTipListV2Return extends IGetInstanceTipsResV1 {} + export interface IBatchCheckInstanceIsConnectableByNameParams extends IBatchCheckInstanceConnectionsReqV1 { project_name: string; diff --git a/packages/shared/lib/api/sqle/service/instance/index.ts b/packages/shared/lib/api/sqle/service/instance/index.ts index 18d2f2a78b..0d710fcfdf 100644 --- a/packages/shared/lib/api/sqle/service/instance/index.ts +++ b/packages/shared/lib/api/sqle/service/instance/index.ts @@ -12,6 +12,8 @@ import { IGetDatabaseDriverOptionsReturn, IGetInstanceTipListV1Params, IGetInstanceTipListV1Return, + IGetInstanceTipListV2Params, + IGetInstanceTipListV2Return, IBatchCheckInstanceIsConnectableByNameParams, IBatchCheckInstanceIsConnectableByNameReturn, ICheckInstanceIsConnectableByNameV1Params, @@ -66,6 +68,21 @@ class InstanceService extends ServiceBase { ); } + public getInstanceTipListV2( + params: IGetInstanceTipListV2Params, + options?: AxiosRequestConfig + ) { + const paramsData = this.cloneDeep(params); + const project_name = paramsData.project_name; + delete paramsData.project_name; + + return this.get( + `/v2/projects/${project_name}/instance_tips`, + paramsData, + options + ); + } + public batchCheckInstanceIsConnectableByName( params: IBatchCheckInstanceIsConnectableByNameParams, options?: AxiosRequestConfig diff --git a/packages/shared/lib/testUtil/mockApi/sqle/auditWhiteList/data.ts b/packages/shared/lib/testUtil/mockApi/sqle/auditWhiteList/data.ts index 5f75ced91a..778367c593 100644 --- a/packages/shared/lib/testUtil/mockApi/sqle/auditWhiteList/data.ts +++ b/packages/shared/lib/testUtil/mockApi/sqle/auditWhiteList/data.ts @@ -1,5 +1,8 @@ -import { IAuditWhitelistResV1 } from '../../../../api/sqle/service/common'; -import { CreateAuditWhitelistReqV1MatchTypeEnum } from '../../../../api/sqle/service/common.enum'; +import { + IAuditWhitelistResV1, + ISQLRuleExceptionResV1 +} from '@actiontech/shared/lib/api/sqle/service/common'; +import { CreateAuditWhitelistReqV1MatchTypeEnum } from '@actiontech/shared/lib/api/sqle/service/common.enum'; export const auditWhiteListMockData: IAuditWhitelistResV1[] = [ { @@ -30,3 +33,21 @@ export const auditWhiteListMockData: IAuditWhitelistResV1[] = [ desc: 'test4' } ]; + +export const sqlRuleExceptionMockData: ISQLRuleExceptionResV1[] = [ + { + sql_rule_exception_id: 11, + project_name: 'default', + instance_id: '1739531854064652288', + instance_name: 'mysql_local_sqle', + sql_fingerprint: 'create table rule_exc_management (id int)', + rule_name: 'ddl_check_pk_not_exist', + rule_desc: '建表语句必须包含主键', + rule_level: 'error', + reason: '标准管理页回归验证', + created_by: 'admin', + created_at: '2026-06-19T02:40:00+00:00', + matched_count: 2, + last_match_time: '2026-06-19T02:50:00+00:00' + } +]; diff --git a/packages/shared/lib/testUtil/mockApi/sqle/auditWhiteList/index.ts b/packages/shared/lib/testUtil/mockApi/sqle/auditWhiteList/index.ts index 37edb88d46..f53936c01a 100644 --- a/packages/shared/lib/testUtil/mockApi/sqle/auditWhiteList/index.ts +++ b/packages/shared/lib/testUtil/mockApi/sqle/auditWhiteList/index.ts @@ -1,12 +1,17 @@ -import audit_whitelist from '../../../../api/sqle/service/audit_whitelist'; -import { MockSpyApy, createSpySuccessResponse } from '../../common'; -import { auditWhiteListMockData } from './data'; +import audit_whitelist from '@actiontech/shared/lib/api/sqle/service/audit_whitelist'; +import { + MockSpyApy, + createSpySuccessResponse +} from '@actiontech/shared/lib/testUtil/mockApi'; +import { auditWhiteListMockData, sqlRuleExceptionMockData } from './data'; class AuditWhiteList implements MockSpyApy { public mockAllApi(): void { this.getAuditWhitelist(); this.deleteAuthWhitelist(); this.addAuthWhitelist(); + this.getSQLRuleException(); + this.deleteSQLRuleException(); } public getAuditWhitelist() { @@ -36,6 +41,22 @@ class AuditWhiteList implements MockSpyApy { spy.mockImplementation(() => createSpySuccessResponse({})); return spy; } + + public getSQLRuleException() { + const spy = jest.spyOn(audit_whitelist, 'getSQLRuleExceptionV1'); + spy.mockImplementation(() => + createSpySuccessResponse({ + data: sqlRuleExceptionMockData + }) + ); + return spy; + } + + public deleteSQLRuleException() { + const spy = jest.spyOn(audit_whitelist, 'deleteSQLRuleExceptionV1'); + spy.mockImplementation(() => createSpySuccessResponse({})); + return spy; + } } export default new AuditWhiteList(); diff --git a/packages/shared/lib/testUtil/mockApi/sqle/instance/index.ts b/packages/shared/lib/testUtil/mockApi/sqle/instance/index.ts index 5d6c56603a..fbd6416a64 100644 --- a/packages/shared/lib/testUtil/mockApi/sqle/instance/index.ts +++ b/packages/shared/lib/testUtil/mockApi/sqle/instance/index.ts @@ -10,6 +10,7 @@ import { class MockInstanceApi implements MockSpyApy { public mockAllApi(): void { this.getInstanceTipList(); + this.getInstanceTipListV2(); this.getInstanceSchemas(); this.batchCheckInstanceIsConnectableByName(); this.getInstance(); @@ -26,6 +27,17 @@ class MockInstanceApi implements MockSpyApy { return spy; } + public getInstanceTipListV2() { + const spy = jest.spyOn(instance, 'getInstanceTipListV2'); + spy.mockImplementation(() => + createSpySuccessResponse({ + data: instanceTipsMockData, + total_nums: instanceTipsMockData.length + }) + ); + return spy; + } + public getInstance() { const spy = jest.spyOn(instance, 'getInstanceV2'); spy.mockImplementation(() => diff --git a/packages/sqle/src/components/ReportDrawer/RuleExceptionDrawer.tsx b/packages/sqle/src/components/ReportDrawer/RuleExceptionDrawer.tsx new file mode 100644 index 0000000000..6ac9fbb843 --- /dev/null +++ b/packages/sqle/src/components/ReportDrawer/RuleExceptionDrawer.tsx @@ -0,0 +1,186 @@ +import { BasicButton, BasicDrawer, BasicInput } from '@actiontech/shared'; +import { DrawerFormLayout } from '@actiontech/shared/lib/data/common'; +import { ResponseCode } from '@actiontech/shared/lib/enum'; +import audit_whitelist from '@actiontech/shared/lib/api/sqle/service/audit_whitelist'; +import instance from '@actiontech/shared/lib/api/sqle/service/instance'; +import { getInstanceTipListV1FunctionalModuleEnum } from '@actiontech/shared/lib/api/sqle/service/instance/index.enum'; +import DBService from '@actiontech/shared/lib/api/base/service/DBService'; +import { ListDBServiceTipsFunctionalModuleEnum } from '@actiontech/shared/lib/api/base/service/DBService/index.enum'; +import { Form, Space, message } from 'antd'; +import { useRequest } from 'ahooks'; +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + RuleExceptionDrawerProps, + RuleExceptionFormFields +} from './index.type'; + +const DUPLICATE_RULE_EXCEPTION_CODE = 4010; + +type RuleExceptionInstanceTip = { + instance_id?: string; + instance_name?: string; +}; + +const RuleExceptionDrawer: React.FC = ({ + open, + data, + context, + onClose, + onCreated +}) => { + const { t } = useTranslation(); + const [form] = Form.useForm(); + const [messageApi, messageContextHolder] = message.useMessage(); + + const { data: instanceTips, loading: instanceTipsLoading } = useRequest( + () => + context?.projectID + ? DBService.ListDBServiceTips({ + project_uid: context.projectID, + filter_db_type: context?.dbType, + functional_module: + ListDBServiceTipsFunctionalModuleEnum.create_workflow + }).then((res) => + (res.data.data ?? []).map((item) => ({ + instance_id: item.id, + instance_name: item.name + })) + ) + : instance + .getInstanceTipListV2({ + project_name: context?.projectName ?? '', + filter_db_type: context?.dbType, + functional_module: + getInstanceTipListV1FunctionalModuleEnum.create_workflow + }) + .then((res) => { + if (res.data.code === ResponseCode.SUCCESS) { + return res.data.data ?? []; + } + return []; + }) + .catch(() => []), + { + ready: open && !!context?.projectName && !context?.instanceId + } + ); + + const resolvedInstanceId = useMemo(() => { + if (context?.instanceId) { + return context.instanceId; + } + const matchedInstance = instanceTips?.find( + (item) => item.instance_name === context?.instanceName + ); + return matchedInstance?.instance_id; + }, [context?.instanceId, context?.instanceName, instanceTips]); + + const lackRequiredContext = + !context?.projectName || + !resolvedInstanceId || + !context?.sqlFingerprint || + !data?.rule_name; + + const { run: submit, loading: submitLoading } = useRequest( + () => form.validateFields(), + { + manual: true, + onSuccess(values) { + audit_whitelist + .createSQLRuleExceptionV1({ + project_name: context!.projectName, + instance_id: `${resolvedInstanceId}`, + sql_fingerprint: context!.sqlFingerprint, + rule_name: data!.rule_name, + rule_desc: data?.desc ?? data?.annotation ?? data?.message, + rule_level: data?.level, + reason: values.reason + }) + .then((res) => { + if (res.data.code === ResponseCode.SUCCESS) { + messageApi.success(t('whitelist.ruleException.addSuccess')); + form.resetFields(); + onCreated?.(); + onClose(); + } else if (res.data.code === DUPLICATE_RULE_EXCEPTION_CODE) { + messageApi.warning(t('whitelist.ruleException.duplicateTips')); + onClose(); + } + }); + } + } + ); + + return ( + <> + {messageContextHolder} + + + {t('common.close')} + + + {t('common.submit')} + + + } + > +
+ + + + + + + + + + + + + + + + + + + + + +
+
+ + ); +}; + +export default RuleExceptionDrawer; diff --git a/packages/sqle/src/components/ReportDrawer/__tests__/__snapshots__/index.test.tsx.snap b/packages/sqle/src/components/ReportDrawer/__tests__/__snapshots__/index.test.tsx.snap index f68fe03fc0..f59777cd70 100644 --- a/packages/sqle/src/components/ReportDrawer/__tests__/__snapshots__/index.test.tsx.snap +++ b/packages/sqle/src/components/ReportDrawer/__tests__/__snapshots__/index.test.tsx.snap @@ -5,7 +5,7 @@ exports[`sqle/components/ReportDrawer render snap is empty 1`] = `
- - - - - - -
-
-
-
-
-
-

- SQL语句 -

-
-
-
- -
-
-
-
-
-
-
-
- - - -`; - -exports[`sqle/components/ReportDrawer render snap open is false 1`] = ` - -
- -`; - -exports[`sqle/components/ReportDrawer render snap when audit rule is exception 1`] = ` - -
-
-
-
-