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..8c941e8365 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,20 @@ export interface IGetInstanceTipListV1Params { export interface IGetInstanceTipListV1Return extends IGetInstanceTipsResV1 {} +export interface IGetInstanceTipListV2Params { + project_name: string; + + filter_db_type?: string; + + filter_by_business?: string; + + filter_workflow_template_id?: string; + + functional_module?: getInstanceTipListV2FunctionalModuleEnum; +} + +export interface IGetInstanceTipListV2Return extends IGetInstanceTipsResV2 {} + 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..c0ad2fa548 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, @@ -24,8 +26,6 @@ import { IListTableBySchemaReturn, IGetTableMetadataParams, IGetTableMetadataReturn, - IGetInstanceTipListV2Params, - IGetInstanceTipListV2Return, IGetInstanceV2Params, IGetInstanceV2Return } from './index.d'; @@ -66,6 +66,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 @@ -180,21 +195,6 @@ 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 getInstanceV2( params: IGetInstanceV2Params, 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..af2bd4b49f 100644 --- a/packages/shared/lib/testUtil/mockApi/sqle/auditWhiteList/data.ts +++ b/packages/shared/lib/testUtil/mockApi/sqle/auditWhiteList/data.ts @@ -1,4 +1,7 @@ -import { IAuditWhitelistResV1 } from '../../../../api/sqle/service/common'; +import { + IAuditWhitelistResV1, + ISQLRuleExceptionResV1 +} from '../../../../api/sqle/service/common.d'; import { CreateAuditWhitelistReqV1MatchTypeEnum } from '../../../../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..09bb9f45b0 100644 --- a/packages/shared/lib/testUtil/mockApi/sqle/auditWhiteList/index.ts +++ b/packages/shared/lib/testUtil/mockApi/sqle/auditWhiteList/index.ts @@ -1,12 +1,14 @@ import audit_whitelist from '../../../../api/sqle/service/audit_whitelist'; -import { MockSpyApy, createSpySuccessResponse } from '../../common'; -import { auditWhiteListMockData } from './data'; +import { MockSpyApy, createSpySuccessResponse } from '../../index'; +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 +38,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/shared/lib/testUtil/mockApi/sqle/operationRecord/index.ts b/packages/shared/lib/testUtil/mockApi/sqle/operationRecord/index.ts index 97302e9c6f..456e5b590b 100644 --- a/packages/shared/lib/testUtil/mockApi/sqle/operationRecord/index.ts +++ b/packages/shared/lib/testUtil/mockApi/sqle/operationRecord/index.ts @@ -1,4 +1,4 @@ -import baseOperationRecord from '../../../../api/base/service/OperationRecord'; +import operationRecord from '../../../../api/sqle/service/OperationRecord'; import { MockSpyApy, createSpySuccessResponse } from '../../common'; import { operationRecordListMockData } from './data'; @@ -9,18 +9,18 @@ class MockOperationRecordApi implements MockSpyApy { } public getOperationRecordList() { - const spy = jest.spyOn(baseOperationRecord, 'GetOperationRecordList'); + const spy = jest.spyOn(operationRecord, 'getOperationRecordListV1'); spy.mockImplementation(() => createSpySuccessResponse({ data: operationRecordListMockData, - total_nums: operationRecordListMockData.length + total: operationRecordListMockData.length }) ); return spy; } public exportOperationRecordList() { - const spy = jest.spyOn(baseOperationRecord, 'ExportOperationRecordList'); + const spy = jest.spyOn(operationRecord, 'getExportOperationRecordListV1'); spy.mockImplementation(() => createSpySuccessResponse({})); return spy; } diff --git a/packages/sqle/src/components/ReportDrawer/RuleExceptionDrawer.tsx b/packages/sqle/src/components/ReportDrawer/RuleExceptionDrawer.tsx new file mode 100644 index 0000000000..7257280743 --- /dev/null +++ b/packages/sqle/src/components/ReportDrawer/RuleExceptionDrawer.tsx @@ -0,0 +1,190 @@ +import { + BasicButton, + BasicDrawer, + BasicInput, + DrawerFormLayout, + ResponseCode +} from '@actiontech/dms-kit'; +import audit_whitelist from '@actiontech/shared/lib/api/sqle/service/audit_whitelist'; +import instance from '@actiontech/shared/lib/api/sqle/service/instance'; +import { getInstanceTipListV2FunctionalModuleEnum } 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: + getInstanceTipListV2FunctionalModuleEnum.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`] = ` - -
-
-
-
-