From 295edb31e7ad73e012009dc6797a32d341c364cc Mon Sep 17 00:00:00 2001 From: actiontech-zihan Date: Mon, 31 Aug 2026 21:58:42 +0800 Subject: [PATCH 1/5] feat(sqle): add SQL manage filter API types and schema autoFetch option Expose filter_schema_name/filter_parse_failed on list params and allow deferring schema tip fetch until dropdown open. Co-authored-by: Cursor --- .../lib/api/sqle/service/SqlManage/index.d.ts | 18 +++++++++++++++++ .../src/hooks/useInstanceSchema/index.tsx | 20 ++++++++++++++++--- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/packages/shared/lib/api/sqle/service/SqlManage/index.d.ts b/packages/shared/lib/api/sqle/service/SqlManage/index.d.ts index 44d93dd0ad..6a83d296fb 100644 --- a/packages/shared/lib/api/sqle/service/SqlManage/index.d.ts +++ b/packages/shared/lib/api/sqle/service/SqlManage/index.d.ts @@ -54,6 +54,8 @@ export interface IGetSqlManageListParams { fuzzy_search_schema_name?: string; + filter_schema_name?: string; + sort_field?: GetSqlManageListSortFieldEnum; sort_order?: GetSqlManageListSortOrderEnum; @@ -102,6 +104,10 @@ export interface IExportSqlManageV1Params { fuzzy_search_schema_name?: string; + filter_schema_name?: string; + + filter_parse_failed?: boolean; + sort_field?: string; sort_order?: exportSqlManageV1SortOrderEnum; @@ -149,6 +155,10 @@ export interface IExportSqlManageRemediationV1Params { fuzzy_search_schema_name?: string; + filter_schema_name?: string; + + filter_parse_failed?: boolean; + extra_filters?: string; /** scan_task scope:与明细 sql_data / 扫描任务报表同结构的 filter_list JSON 字符串 */ @@ -211,6 +221,10 @@ export interface IGetSqlManageListV2Params { fuzzy_search_schema_name?: string; + filter_schema_name?: string; + + filter_parse_failed?: boolean; + sort_field?: string; sort_order?: GetSqlManageListV2SortOrderEnum; @@ -255,6 +269,10 @@ export interface IGetSqlManageStatisticsV2Params { fuzzy_search_schema_name?: string; + filter_schema_name?: string; + + filter_parse_failed?: boolean; + extra_filters?: string; } diff --git a/packages/sqle/src/hooks/useInstanceSchema/index.tsx b/packages/sqle/src/hooks/useInstanceSchema/index.tsx index ec9cd49271..13876ee0bc 100644 --- a/packages/sqle/src/hooks/useInstanceSchema/index.tsx +++ b/packages/sqle/src/hooks/useInstanceSchema/index.tsx @@ -4,7 +4,17 @@ import React from 'react'; import { ResponseCode } from '../../data/common'; import instance from '@actiontech/shared/lib/api/sqle/service/instance'; -const useInstanceSchema = (projectName: string, instanceName?: string) => { +type UseInstanceSchemaOptions = { + /** 默认 true:instanceName 变化时自动拉取。SQL 管控 Schema 筛选用 false,仅点开下拉时拉取。 */ + autoFetch?: boolean; +}; + +const useInstanceSchema = ( + projectName: string, + instanceName?: string, + options?: UseInstanceSchemaOptions +) => { + const autoFetch = options?.autoFetch !== false; const [schemaList, setSchemaList] = React.useState([]); const [loading, { setTrue, setFalse }] = useBoolean(); @@ -38,8 +48,12 @@ const useInstanceSchema = (projectName: string, instanceName?: string) => { }, [schemaList]); React.useEffect(() => { - updateSchemaList(); - }, [instanceName, updateSchemaList]); + if (autoFetch) { + updateSchemaList(); + return; + } + setSchemaList([]); + }, [autoFetch, instanceName, updateSchemaList]); return { schemaList, From ba8fbfa3d9974e1452e5c9b48ea667a3033bf229 Mon Sep 17 00:00:00 2001 From: actiontech-zihan Date: Mon, 31 Aug 2026 21:58:42 +0800 Subject: [PATCH 2/5] feat(sql-manage): add schema dual-mode filter and parse-failed toggle Support exact schema input/select without prefetching schemas, and pass filter_parse_failed when the toolbar switch is on. Co-authored-by: Cursor --- .../sqle/src/locale/en-US/sqlManagement.ts | 2 + .../sqle/src/locale/zh-CN/sqlManagement.ts | 2 + .../component/SQLEEIndex/column.tsx | 15 ++- .../hooks/useGetTableFilterInfo.tsx | 127 +++++++++++++++++- .../hooks/useSqlManageSourceExtra.tsx | 25 +++- .../component/SQLEEIndex/index.data.ts | 18 ++- .../component/SQLEEIndex/index.tsx | 26 +++- .../component/SQLEEIndex/sourceExtra.data.ts | 1 + 8 files changed, 201 insertions(+), 15 deletions(-) diff --git a/packages/sqle/src/locale/en-US/sqlManagement.ts b/packages/sqle/src/locale/en-US/sqlManagement.ts index fafc42f072..fc4d89b7b0 100644 --- a/packages/sqle/src/locale/en-US/sqlManagement.ts +++ b/packages/sqle/src/locale/en-US/sqlManagement.ts @@ -109,6 +109,7 @@ export default { }, business: 'Business', instanceName: 'DB instance', + schema: 'Schema', source: { label: 'Source', auditPlan: 'Scan task', @@ -124,6 +125,7 @@ export default { }, assignee: 'Related to me', viewHighPrioritySql: 'View high priority SQL', + parseFailed: 'Syntax error or unsupported by parser', rule: 'Audit rule', ruleSelectDbTypeFirst: 'Database type', ruleDbTypeFilter: 'Database type', diff --git a/packages/sqle/src/locale/zh-CN/sqlManagement.ts b/packages/sqle/src/locale/zh-CN/sqlManagement.ts index f733cdd22a..c2a803789c 100644 --- a/packages/sqle/src/locale/zh-CN/sqlManagement.ts +++ b/packages/sqle/src/locale/zh-CN/sqlManagement.ts @@ -112,6 +112,7 @@ export default { }, business: '业务', instanceName: '数据源', + schema: 'Schema', source: { label: '来源', auditPlan: '扫描任务', @@ -127,6 +128,7 @@ export default { }, assignee: '与我相关', viewHighPrioritySql: '查看高优先级SQL', + parseFailed: '语法错误或解析器不支持', rule: '审核规则', ruleSelectDbTypeFirst: '数据源类型', ruleDbTypeFilter: '数据源类型', diff --git a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/column.tsx b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/column.tsx index 02e8b5822c..0bb32f86d3 100644 --- a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/column.tsx +++ b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/column.tsx @@ -27,7 +27,10 @@ import { formatTime } from '@actiontech/shared/lib/utils/Common'; import { SQLAuditRecordListUrlParamsKey } from './index.data'; export type SqlManagementTableFilterParamType = PageInfoWithoutIndexAndSize< IGetSqlManageListV2Params, - 'fuzzy_search_sql_fingerprint' | 'filter_status' | 'project_name' + | 'fuzzy_search_sql_fingerprint' + | 'filter_status' + | 'project_name' + | 'filter_parse_failed' > & Record; @@ -35,6 +38,7 @@ export type ExtraFilterMetaType = ISqlManage & { filter_business?: string; filter_source?: string; filter_instance_id?: string; + filter_schema_name?: string; filter_audit_level?: string; filter_rule_name?: string; time?: string; @@ -70,6 +74,15 @@ export const ExtraFilterMeta: () => ActiontechTableFilterMeta< checked: false } ], + [ + 'filter_schema_name', + { + filterCustomType: 'input', + filterKey: 'filter_schema_name', + filterLabel: t('sqlManagement.table.filter.schema'), + checked: false + } + ], [ 'filter_source', { diff --git a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/hooks/useGetTableFilterInfo.tsx b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/hooks/useGetTableFilterInfo.tsx index 33b5722dcf..86ddb7263f 100644 --- a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/hooks/useGetTableFilterInfo.tsx +++ b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/hooks/useGetTableFilterInfo.tsx @@ -1,13 +1,17 @@ -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import type { ReactElement } from 'react'; import { useTranslation } from 'react-i18next'; import { FilterCustomProps } from '@actiontech/shared/lib/components/ActiontechTable'; -import { UpdateTableFilterInfoType } from '@actiontech/shared/lib/components/ActiontechTable/index.type'; +import { + TypeFilterElement, + UpdateTableFilterInfoType +} from '@actiontech/shared/lib/components/ActiontechTable/index.type'; import { useCurrentProject, useProjectBusinessTips } from '@actiontech/shared/lib/global'; import useInstance from '../../../../../hooks/useInstance'; +import useInstanceSchema from '../../../../../hooks/useInstanceSchema'; import useRuleTips, { extractDbTypeFromRuleSelectValue } from '../../../../../hooks/useRuleTips'; @@ -19,6 +23,8 @@ import useSourceTips from './useSourceTips'; import useStaticStatus from './useStaticStatus'; import RuleTipsFilterDropdownExtra from './RuleTipsFilterDropdownExtra'; +const MYSQL_INSTANCE_TYPE = 'MySQL'; + type UseGetTableFilterInfoParams = { filterRuleName?: string; tableFilterInfo?: SqlManagementTableFilterParamType; @@ -28,7 +34,8 @@ type UseGetTableFilterInfoParams = { const useGetTableFilterInfo = (params?: UseGetTableFilterInfoParams) => { const { t } = useTranslation(); const { projectName } = useCurrentProject(); - const { filterRuleName } = params ?? {}; + const { filterRuleName, tableFilterInfo, updateTableFilterInfo } = + params ?? {}; const { generateAuditLevelSelectOptions } = useStaticStatus(); @@ -37,6 +44,7 @@ const useGetTableFilterInfo = (params?: UseGetTableFilterInfoParams) => { const { instanceIDOptions, + instanceList, updateInstanceList, loading: getInstanceLoading } = useInstance(); @@ -61,6 +69,61 @@ const useGetTableFilterInfo = (params?: UseGetTableFilterInfoParams) => { const [ruleKeyword, setRuleKeyword] = useState(''); const [ruleDropdownOpen, setRuleDropdownOpen] = useState(false); + const filterInstanceId = tableFilterInfo?.filter_instance_id as + | string + | undefined; + + const selectedInstance = useMemo( + () => + instanceList.find( + (item) => String(item.instance_id) === String(filterInstanceId ?? '') + ), + [filterInstanceId, instanceList] + ); + + const isMySQLSchemaSelectMode = + !!selectedInstance && + selectedInstance.instance_type === MYSQL_INSTANCE_TYPE; + + const schemaFilterCustomType: TypeFilterElement = isMySQLSchemaSelectMode + ? 'select' + : 'input'; + + const { + schemaList, + loading: getSchemaLoading, + updateSchemaList + } = useInstanceSchema(projectName, selectedInstance?.instance_name, { + autoFetch: false + }); + + const prevFilterInstanceIdRef = useRef(filterInstanceId); + + const clearSchemaFilterValue = useCallback(() => { + if (!updateTableFilterInfo) { + return; + } + // useTableRequestParams 会先无参调用 updater 做浅比较,再交给 setState; + // 故无参时必须安全返回,真正清空依赖 React setState 传入的 prev。 + const updater = ( + prev?: SqlManagementTableFilterParamType + ): SqlManagementTableFilterParamType => { + if (!prev) { + return {} as SqlManagementTableFilterParamType; + } + if (prev.filter_schema_name === undefined) { + return prev; + } + return { + ...prev, + filter_schema_name: undefined + }; + }; + updateTableFilterInfo( + updater as unknown as SqlManagementTableFilterParamType + ); + }, [updateTableFilterInfo]); + useEffect(() => { updateInstanceList({ project_name: projectName }); updateRuleTips(projectName); @@ -82,6 +145,15 @@ const useGetTableFilterInfo = (params?: UseGetTableFilterInfoParams) => { } }, [filterRuleName]); + useEffect(() => { + const prev = prevFilterInstanceIdRef.current; + if (prev === filterInstanceId) { + return; + } + prevFilterInstanceIdRef.current = filterInstanceId; + clearSchemaFilterValue(); + }, [clearSchemaFilterValue, filterInstanceId]); + const onDbTypeChange = useCallback((dbType?: string) => { // 仅筛选下拉选项,不改 tableFilterInfo,避免关闭外层「审核规则」面板 setSelectedDbType(dbType); @@ -102,6 +174,22 @@ const useGetTableFilterInfo = (params?: UseGetTableFilterInfoParams) => { setRuleDropdownOpen(open); }, []); + const onSchemaDropdownVisibleChange = useCallback( + (open: boolean) => { + if (open && isMySQLSchemaSelectMode) { + updateSchemaList(); + } + }, + [isMySQLSchemaSelectMode, updateSchemaList] + ); + + const onInstanceFilterChange = useCallback(() => { + // Select 内部随后写入 filter_instance_id;下一 macrotask 再清 Schema,保证读到新 instance + setTimeout(() => { + clearSchemaFilterValue(); + }, 0); + }, [clearSchemaFilterValue]); + const ruleSelectOptions = useMemo(() => { const options = generateFlatRuleOptionsByDbType( selectedDbType, @@ -160,7 +248,25 @@ const useGetTableFilterInfo = (params?: UseGetTableFilterInfoParams) => { ] ); + const schemaSelectOptions = useMemo( + () => + schemaList.map((schema) => ({ + label: schema, + value: schema + })), + [schemaList] + ); + const filterCustomProps = useMemo(() => { + const schemaProps: FilterCustomProps = isMySQLSchemaSelectMode + ? { + options: schemaSelectOptions, + loading: getSchemaLoading, + allowClear: true, + onDropdownVisibleChange: onSchemaDropdownVisibleChange + } + : {}; + return new Map([ [ 'filter_business', @@ -168,8 +274,13 @@ const useGetTableFilterInfo = (params?: UseGetTableFilterInfoParams) => { ], [ 'filter_instance_id', - { options: instanceIDOptions, loading: getInstanceLoading } + { + options: instanceIDOptions, + loading: getInstanceLoading, + onChange: onInstanceFilterChange + } ], + ['filter_schema_name', schemaProps], [ 'filter_source', { options: generateSourceSelectOptions, loading: getSourceTipsLoading } @@ -193,6 +304,11 @@ const useGetTableFilterInfo = (params?: UseGetTableFilterInfoParams) => { getProjectBusinessLoading, instanceIDOptions, getInstanceLoading, + onInstanceFilterChange, + isMySQLSchemaSelectMode, + schemaSelectOptions, + getSchemaLoading, + onSchemaDropdownVisibleChange, generateSourceSelectOptions, getSourceTipsLoading, generateAuditLevelSelectOptions, @@ -204,7 +320,8 @@ const useGetTableFilterInfo = (params?: UseGetTableFilterInfoParams) => { ]); return { - filterCustomProps + filterCustomProps, + schemaFilterCustomType }; }; diff --git a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/hooks/useSqlManageSourceExtra.tsx b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/hooks/useSqlManageSourceExtra.tsx index 5703edc52a..d0371dbc16 100644 --- a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/hooks/useSqlManageSourceExtra.tsx +++ b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/hooks/useSqlManageSourceExtra.tsx @@ -53,11 +53,12 @@ const useSqlManageSourceExtra = ({ const [sourceExtra, setSourceExtra] = useState(); const prevSourceExtraFilterNamesRef = useRef([]); const { tableFilterMetaFactory } = useBackendTable(); - const { filterCustomProps: staticFilterCustomProps } = useGetTableFilterInfo({ - filterRuleName: tableFilterInfo.filter_rule_name, - tableFilterInfo, - updateTableFilterInfo - }); + const { filterCustomProps: staticFilterCustomProps, schemaFilterCustomType } = + useGetTableFilterInfo({ + filterRuleName: tableFilterInfo.filter_rule_name, + tableFilterInfo, + updateTableFilterInfo + }); const sourceExtraActive = canApplySourceExtraFilters( sourceExtra, @@ -100,6 +101,13 @@ const useSqlManageSourceExtra = ({ SqlManagementFilterMetaRecordType, SqlManagementTableFilterParamType >; + const schemaMeta = map.get('filter_schema_name'); + if (schemaMeta) { + map.set('filter_schema_name', { + ...schemaMeta, + filterCustomType: schemaFilterCustomType + }); + } dynamicSourceExtraFilterMeta?.extraTableFilterMeta.forEach((value, key) => { const labelText = typeof value.filterLabel === 'string' ? value.filterLabel : ''; @@ -115,7 +123,12 @@ const useSqlManageSourceExtra = ({ }); }); return map; - }, [staticExtraFilterMeta, dynamicSourceExtraFilterMeta, t]); + }, [ + staticExtraFilterMeta, + dynamicSourceExtraFilterMeta, + schemaFilterCustomType, + t + ]); const filterColumns = baseColumns as ActiontechTableColumn< SqlManagementFilterMetaRecordType, diff --git a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.data.ts b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.data.ts index 365f03b5c0..69ae104cfc 100644 --- a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.data.ts +++ b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.data.ts @@ -9,13 +9,17 @@ export const SQLAuditRecordIDValuesSplit = ','; export const defaultActionButton = ({ isAssigneeSelf, isHighPriority, + isParseFailedFilter, setAssigneeSelf, - setIsHighPriority + setIsHighPriority, + setIsParseFailedFilter }: { isAssigneeSelf: boolean; setAssigneeSelf: (value: boolean) => void; isHighPriority: boolean; setIsHighPriority: (value: boolean) => void; + isParseFailedFilter: boolean; + setIsParseFailedFilter: (value: boolean) => void; }) => [ { key: 'is-high-priority', @@ -27,6 +31,18 @@ export const defaultActionButton = ({ } } }, + { + key: 'parse-failed-filter', + text: t('sqlManagement.table.filter.parseFailed'), + buttonProps: { + className: isParseFailedFilter + ? 'switch-btn-active' + : 'switch-btn-default', + onClick: () => { + setIsParseFailedFilter(!isParseFailedFilter); + } + } + }, { key: 'assignment-self', text: t('sqlManagement.table.filter.assignee'), diff --git a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.tsx b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.tsx index 8ed301db1a..295c6fd1ce 100644 --- a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.tsx +++ b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.tsx @@ -103,6 +103,7 @@ const SQLEEIndex = () => { const [isAssigneeSelf, setAssigneeSelf] = useState(false); const [isHighPriority, setIsHighPriority] = useState(false); + const [isParseFailedFilter, setIsParseFailedFilter] = useState(false); const { tableFilterInfo, updateTableFilterInfo, @@ -212,6 +213,14 @@ const SQLEEIndex = () => { [resetPageIndex] ); + const onParseFailedFilterChange = useCallback( + (value: boolean) => { + setIsParseFailedFilter(value); + resetPageIndex(); + }, + [resetPageIndex] + ); + const applyOptimisticWrite = useCallback( (payload: SqlManageOptimisticWritePayload) => { const ids = payload.ids.filter((id) => Number.isFinite(id)); @@ -394,6 +403,12 @@ const SQLEEIndex = () => { const staticFilters = pickStaticSqlManageFilters( otherTableFilterInfo as Record ); + if ( + staticFilters.filter_schema_name === '' || + staticFilters.filter_schema_name === undefined + ) { + delete staticFilters.filter_schema_name; + } return { ...(staticFilters as Partial), @@ -410,6 +425,7 @@ const SQLEEIndex = () => { filter_priority: isHighPriority ? GetSqlManageListV2FilterPriorityEnum.high : undefined, + filter_parse_failed: isParseFailedFilter ? true : undefined, extra_filters: buildExtraFiltersForRequest(tableFilterInfo) }; // eslint-disable-next-line react-hooks/exhaustive-deps @@ -424,6 +440,7 @@ const SQLEEIndex = () => { isAssigneeSelf, uid, isHighPriority, + isParseFailedFilter, resolveListSortField ]); @@ -457,6 +474,7 @@ const SQLEEIndex = () => { filterStatus, isAssigneeSelf, isHighPriority, + isParseFailedFilter, tableFilterInfo, sortInfo ], @@ -524,6 +542,7 @@ const SQLEEIndex = () => { filterStatus, isAssigneeSelf, isHighPriority, + isParseFailedFilter, tableFilterInfo ] } @@ -813,7 +832,8 @@ const SQLEEIndex = () => { filter_rule_name: listParams.filter_rule_name, filter_priority: listParams.filter_priority, fuzzy_search_endpoint: listParams.fuzzy_search_endpoint, - fuzzy_search_schema_name: listParams.fuzzy_search_schema_name, + filter_schema_name: listParams.filter_schema_name, + filter_parse_failed: listParams.filter_parse_failed, extra_filters: listParams.extra_filters }, { responseType: 'blob' } @@ -901,8 +921,10 @@ const SQLEEIndex = () => { const defaultButton = defaultActionButton({ isAssigneeSelf, isHighPriority, + isParseFailedFilter, setAssigneeSelf: onAssigneeSelfChange, - setIsHighPriority: onHighPriorityChange + setIsHighPriority: onHighPriorityChange, + setIsParseFailedFilter: onParseFailedFilterChange }); const actionButton = actionsButtonData( selectedRowKeys?.length === 0, diff --git a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/sourceExtra.data.ts b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/sourceExtra.data.ts index b54bf8bf90..2032524cca 100644 --- a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/sourceExtra.data.ts +++ b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/sourceExtra.data.ts @@ -27,6 +27,7 @@ export const SQL_MANAGE_STATIC_FILTER_KEYS = [ 'filter_business', 'filter_source', 'filter_instance_id', + 'filter_schema_name', 'filter_audit_level', 'filter_rule_name', 'filter_last_audit_start_time_from', From 81f6040017e9c3adec59810c82051547402745db Mon Sep 17 00:00:00 2001 From: actiontech-zihan Date: Mon, 31 Aug 2026 21:58:42 +0800 Subject: [PATCH 3/5] test(sql-manage): cover parse-failed filter request and update snapshots Co-authored-by: Cursor --- .../__snapshots__/index.test.tsx.snap | 13 +++ .../__snapshots__/index.test.tsx.snap | 85 +++++++++++++++++++ .../component/SQLEEIndex/index.test.tsx | 15 ++++ 3 files changed, 113 insertions(+) diff --git a/packages/sqle/src/page/SqlManagement/__snapshots__/index.test.tsx.snap b/packages/sqle/src/page/SqlManagement/__snapshots__/index.test.tsx.snap index eb0cf9d3f8..28651804c0 100644 --- a/packages/sqle/src/page/SqlManagement/__snapshots__/index.test.tsx.snap +++ b/packages/sqle/src/page/SqlManagement/__snapshots__/index.test.tsx.snap @@ -329,6 +329,19 @@ exports[`page/SqlManagement render sql management page 1`] = ` +
+ +
+
+ +
+
+ + + Schema + + + +
+
+ +
+
+ +
+
+ +
+
+ +
diff --git a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.test.tsx b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.test.tsx index 2a18affce9..99460ca40c 100644 --- a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.test.tsx +++ b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.test.tsx @@ -210,6 +210,21 @@ describe('page/SqlManagement/SQLEEIndex', () => { }); }); + it('filter data with parse failed', async () => { + const request = sqlManage.getSqlManageList(); + superRender(); + expect(request).toHaveBeenCalled(); + expect(request).toHaveBeenCalledWith( + expect.not.objectContaining({ filter_parse_failed: true }) + ); + fireEvent.click(screen.getByText('语法错误或解析器不支持')); + await act(async () => jest.advanceTimersByTime(3000)); + expect(request).toHaveBeenCalledWith({ + ...requestParams, + filter_parse_failed: true + }); + }); + it('filter data with status', async () => { const request = sqlManage.getSqlManageList(); superRender(); From b1c297f77415f1eb247cad4047747957b80cf6c5 Mon Sep 17 00:00:00 2001 From: actiontech-zihan Date: Tue, 1 Sep 2026 15:50:35 +0800 Subject: [PATCH 4/5] feat(sql-manage): amend filters for parse-failed rule select and schema any-type Move parse-failed into audit-rule select with sentinel mapping, polish high-priority/schema tips, and open schema dropdown for any selected instance. Co-authored-by: Cursor --- .../sqle/src/locale/en-US/sqlManagement.ts | 2 + .../sqle/src/locale/zh-CN/sqlManagement.ts | 2 + .../hooks/useGetTableFilterInfo.tsx | 48 +++++++++++-------- .../hooks/useSqlManageSourceExtra.tsx | 10 +++- .../{index.data.ts => index.data.tsx} | 30 +++++------- .../component/SQLEEIndex/index.tsx | 37 +++++++------- 6 files changed, 69 insertions(+), 60 deletions(-) rename packages/sqle/src/page/SqlManagement/component/SQLEEIndex/{index.data.ts => index.data.tsx} (79%) diff --git a/packages/sqle/src/locale/en-US/sqlManagement.ts b/packages/sqle/src/locale/en-US/sqlManagement.ts index fc4d89b7b0..3b8212a2b5 100644 --- a/packages/sqle/src/locale/en-US/sqlManagement.ts +++ b/packages/sqle/src/locale/en-US/sqlManagement.ts @@ -124,8 +124,10 @@ export default { notice: 'Notice' }, assignee: 'Related to me', + highPriority: 'High priority', viewHighPrioritySql: 'View high priority SQL', parseFailed: 'Syntax error or unsupported by parser', + schemaSelectTip: 'Select a data source to enable the dropdown', rule: 'Audit rule', ruleSelectDbTypeFirst: 'Database type', ruleDbTypeFilter: 'Database type', diff --git a/packages/sqle/src/locale/zh-CN/sqlManagement.ts b/packages/sqle/src/locale/zh-CN/sqlManagement.ts index c2a803789c..091ec44b24 100644 --- a/packages/sqle/src/locale/zh-CN/sqlManagement.ts +++ b/packages/sqle/src/locale/zh-CN/sqlManagement.ts @@ -127,8 +127,10 @@ export default { notice: '提示' }, assignee: '与我相关', + highPriority: '高优先级', viewHighPrioritySql: '查看高优先级SQL', parseFailed: '语法错误或解析器不支持', + schemaSelectTip: '选择数据源后可下拉选择', rule: '审核规则', ruleSelectDbTypeFirst: '数据源类型', ruleDbTypeFilter: '数据源类型', diff --git a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/hooks/useGetTableFilterInfo.tsx b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/hooks/useGetTableFilterInfo.tsx index 86ddb7263f..ed518e25bb 100644 --- a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/hooks/useGetTableFilterInfo.tsx +++ b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/hooks/useGetTableFilterInfo.tsx @@ -22,8 +22,8 @@ import { import useSourceTips from './useSourceTips'; import useStaticStatus from './useStaticStatus'; import RuleTipsFilterDropdownExtra from './RuleTipsFilterDropdownExtra'; - -const MYSQL_INSTANCE_TYPE = 'MySQL'; +import { PARSE_FAILED_RULE_SELECT_VALUE } from '../index.data'; +import { AuditLevelRuleOptionLabel } from '../../../../../components/AuditResultMessage/AuditLevelIcon'; type UseGetTableFilterInfoParams = { filterRuleName?: string; @@ -81,11 +81,10 @@ const useGetTableFilterInfo = (params?: UseGetTableFilterInfoParams) => { [filterInstanceId, instanceList] ); - const isMySQLSchemaSelectMode = - !!selectedInstance && - selectedInstance.instance_type === MYSQL_INSTANCE_TYPE; + // 开关只看是否选中数据源;禁止按库型 / MySQL 白名单分支(TDSQL 等须同样下拉) + const isSchemaSelectMode = !!selectedInstance; - const schemaFilterCustomType: TypeFilterElement = isMySQLSchemaSelectMode + const schemaFilterCustomType: TypeFilterElement = isSchemaSelectMode ? 'select' : 'input'; @@ -176,11 +175,11 @@ const useGetTableFilterInfo = (params?: UseGetTableFilterInfoParams) => { const onSchemaDropdownVisibleChange = useCallback( (open: boolean) => { - if (open && isMySQLSchemaSelectMode) { + if (open && isSchemaSelectMode) { updateSchemaList(); } }, - [isMySQLSchemaSelectMode, updateSchemaList] + [isSchemaSelectMode, updateSchemaList] ); const onInstanceFilterChange = useCallback(() => { @@ -191,25 +190,33 @@ const useGetTableFilterInfo = (params?: UseGetTableFilterInfoParams) => { }, [clearSchemaFilterValue]); const ruleSelectOptions = useMemo(() => { + const parseFailedText = t('sqlManagement.table.filter.parseFailed'); + const parseFailedOption = { + label: , + text: `${parseFailedText} warn`, + value: PARSE_FAILED_RULE_SELECT_VALUE + }; const options = generateFlatRuleOptionsByDbType( selectedDbType, selectedRuleLevel ); const keyword = ruleKeyword.trim().toLowerCase(); - if (!keyword) { - return options; - } - return options.filter((option) => { - const haystack = `${option.text ?? ''} ${ - option.value ?? '' - }`.toLowerCase(); - return haystack.includes(keyword); - }); + const filtered = !keyword + ? options + : options.filter((option) => { + const haystack = `${option.text ?? ''} ${ + option.value ?? '' + }`.toLowerCase(); + return haystack.includes(keyword); + }); + // 固定项置顶,不依赖 rule_tips / 库型 / 关键词 + return [parseFailedOption, ...filtered]; }, [ generateFlatRuleOptionsByDbType, ruleKeyword, selectedDbType, - selectedRuleLevel + selectedRuleLevel, + t ]); const ruleLevelFilterOptions = useMemo(() => { @@ -258,7 +265,7 @@ const useGetTableFilterInfo = (params?: UseGetTableFilterInfoParams) => { ); const filterCustomProps = useMemo(() => { - const schemaProps: FilterCustomProps = isMySQLSchemaSelectMode + const schemaProps: FilterCustomProps = isSchemaSelectMode ? { options: schemaSelectOptions, loading: getSchemaLoading, @@ -292,6 +299,7 @@ const useGetTableFilterInfo = (params?: UseGetTableFilterInfoParams) => { { options: ruleSelectOptions, loading: getRuleTipsLoading, + allowClear: true, popupMatchSelectWidth: 400, open: ruleDropdownOpen, onDropdownVisibleChange: onRuleDropdownVisibleChange, @@ -305,7 +313,7 @@ const useGetTableFilterInfo = (params?: UseGetTableFilterInfoParams) => { instanceIDOptions, getInstanceLoading, onInstanceFilterChange, - isMySQLSchemaSelectMode, + isSchemaSelectMode, schemaSelectOptions, getSchemaLoading, onSchemaDropdownVisibleChange, diff --git a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/hooks/useSqlManageSourceExtra.tsx b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/hooks/useSqlManageSourceExtra.tsx index d0371dbc16..77a777cd04 100644 --- a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/hooks/useSqlManageSourceExtra.tsx +++ b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/hooks/useSqlManageSourceExtra.tsx @@ -105,7 +105,15 @@ const useSqlManageSourceExtra = ({ if (schemaMeta) { map.set('filter_schema_name', { ...schemaMeta, - filterCustomType: schemaFilterCustomType + filterCustomType: schemaFilterCustomType, + // tip 仅挂在 prefix 文字;CustomInput/Select 的输入主体不携带 title + filterLabel: ( + + {t('sqlManagement.table.filter.schema')} + + ) }); } dynamicSourceExtraFilterMeta?.extraTableFilterMeta.forEach((value, key) => { diff --git a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.data.ts b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.data.tsx similarity index 79% rename from packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.data.ts rename to packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.data.tsx index 69ae104cfc..4da6837fc1 100644 --- a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.data.ts +++ b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.data.tsx @@ -1,4 +1,8 @@ import { t } from '../../../../locale'; +import { BasicToolTips } from '@actiontech/shared'; + +/** UI-only sentinel for「语法错误或解析器不支持」in 审核规则 Select; never sent as filter_rule_name */ +export const PARSE_FAILED_RULE_SELECT_VALUE = '__PARSE_FAILED__'; export const SQLAuditRecordListUrlParamsKey = { SQLAuditRecordID: 'SQLAuditRecordID' @@ -9,21 +13,23 @@ export const SQLAuditRecordIDValuesSplit = ','; export const defaultActionButton = ({ isAssigneeSelf, isHighPriority, - isParseFailedFilter, setAssigneeSelf, - setIsHighPriority, - setIsParseFailedFilter + setIsHighPriority }: { isAssigneeSelf: boolean; setAssigneeSelf: (value: boolean) => void; isHighPriority: boolean; setIsHighPriority: (value: boolean) => void; - isParseFailedFilter: boolean; - setIsParseFailedFilter: (value: boolean) => void; }) => [ { key: 'is-high-priority', - text: t('sqlManagement.table.filter.viewHighPrioritySql'), + text: ( + + {t('sqlManagement.table.filter.highPriority')} + + ), buttonProps: { className: isHighPriority ? 'switch-btn-active' : 'switch-btn-default', onClick: () => { @@ -31,18 +37,6 @@ export const defaultActionButton = ({ } } }, - { - key: 'parse-failed-filter', - text: t('sqlManagement.table.filter.parseFailed'), - buttonProps: { - className: isParseFailedFilter - ? 'switch-btn-active' - : 'switch-btn-default', - onClick: () => { - setIsParseFailedFilter(!isParseFailedFilter); - } - } - }, { key: 'assignment-self', text: t('sqlManagement.table.filter.assignee'), diff --git a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.tsx b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.tsx index 295c6fd1ce..a0e550f482 100644 --- a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.tsx +++ b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.tsx @@ -50,7 +50,11 @@ import EventEmitter from '../../../../utils/EventEmitter'; import { DB_TYPE_RULE_NAME_SEPARATOR } from '../../../../hooks/useRuleTips'; import useSqlManagementRedux from './hooks/useSqlManagementRedux'; import useBatchIgnoreOrSolve from './hooks/useBatchIgnoreOrSolve'; -import { actionsButtonData, defaultActionButton } from './index.data'; +import { + actionsButtonData, + defaultActionButton, + PARSE_FAILED_RULE_SELECT_VALUE +} from './index.data'; import { DownArrowLineOutlined } from '@actiontech/icons'; import useSqlManagementExceptionRedux from '../../../SqlManagementException/hooks/useSqlManagementExceptionRedux'; import useWhitelistRedux from '../../../Whitelist/hooks/useWhitelistRedux'; @@ -103,7 +107,6 @@ const SQLEEIndex = () => { const [isAssigneeSelf, setAssigneeSelf] = useState(false); const [isHighPriority, setIsHighPriority] = useState(false); - const [isParseFailedFilter, setIsParseFailedFilter] = useState(false); const { tableFilterInfo, updateTableFilterInfo, @@ -213,14 +216,6 @@ const SQLEEIndex = () => { [resetPageIndex] ); - const onParseFailedFilterChange = useCallback( - (value: boolean) => { - setIsParseFailedFilter(value); - resetPageIndex(); - }, - [resetPageIndex] - ); - const applyOptimisticWrite = useCallback( (payload: SqlManageOptimisticWritePayload) => { const ids = payload.ids.filter((id) => Number.isFinite(id)); @@ -410,14 +405,19 @@ const SQLEEIndex = () => { delete staticFilters.filter_schema_name; } + const isParseFailedRuleSelected = + filter_rule_name === PARSE_FAILED_RULE_SELECT_VALUE; + return { ...(staticFilters as Partial), ...pagination, ...getCurrentSortParams(sortInfo), - filter_db_type: filter_rule_name?.split(DB_TYPE_RULE_NAME_SEPARATOR)?.[0], - filter_rule_name: filter_rule_name?.split( - DB_TYPE_RULE_NAME_SEPARATOR - )?.[1], + filter_db_type: isParseFailedRuleSelected + ? undefined + : filter_rule_name?.split(DB_TYPE_RULE_NAME_SEPARATOR)?.[0], + filter_rule_name: isParseFailedRuleSelected + ? undefined + : filter_rule_name?.split(DB_TYPE_RULE_NAME_SEPARATOR)?.[1], filter_status: filterStatus === 'all' ? undefined : filterStatus, fuzzy_search_sql_fingerprint: searchKeyword, project_name: projectName, @@ -425,7 +425,7 @@ const SQLEEIndex = () => { filter_priority: isHighPriority ? GetSqlManageListV2FilterPriorityEnum.high : undefined, - filter_parse_failed: isParseFailedFilter ? true : undefined, + filter_parse_failed: isParseFailedRuleSelected ? true : undefined, extra_filters: buildExtraFiltersForRequest(tableFilterInfo) }; // eslint-disable-next-line react-hooks/exhaustive-deps @@ -440,7 +440,6 @@ const SQLEEIndex = () => { isAssigneeSelf, uid, isHighPriority, - isParseFailedFilter, resolveListSortField ]); @@ -474,7 +473,6 @@ const SQLEEIndex = () => { filterStatus, isAssigneeSelf, isHighPriority, - isParseFailedFilter, tableFilterInfo, sortInfo ], @@ -542,7 +540,6 @@ const SQLEEIndex = () => { filterStatus, isAssigneeSelf, isHighPriority, - isParseFailedFilter, tableFilterInfo ] } @@ -921,10 +918,8 @@ const SQLEEIndex = () => { const defaultButton = defaultActionButton({ isAssigneeSelf, isHighPriority, - isParseFailedFilter, setAssigneeSelf: onAssigneeSelfChange, - setIsHighPriority: onHighPriorityChange, - setIsParseFailedFilter: onParseFailedFilterChange + setIsHighPriority: onHighPriorityChange }); const actionButton = actionsButtonData( selectedRowKeys?.length === 0, From 29758f4499f76e99c8f4d083f725bb4ca6d8fe99 Mon Sep 17 00:00:00 2001 From: actiontech-zihan Date: Tue, 1 Sep 2026 15:50:35 +0800 Subject: [PATCH 5/5] test(sql-manage): cover parse-failed rule select and update snapshots Align unit tests and snapshots with audit-rule fixed item and high-priority label text. Co-authored-by: Cursor --- .../__snapshots__/index.test.tsx.snap | 27 +-- .../__snapshots__/index.test.tsx.snap | 201 +++++++++++------- .../__tests__/useGetTableFilterInfo.test.ts | 22 +- .../component/SQLEEIndex/index.test.tsx | 48 ++++- 4 files changed, 189 insertions(+), 109 deletions(-) diff --git a/packages/sqle/src/page/SqlManagement/__snapshots__/index.test.tsx.snap b/packages/sqle/src/page/SqlManagement/__snapshots__/index.test.tsx.snap index 28651804c0..313b2f5083 100644 --- a/packages/sqle/src/page/SqlManagement/__snapshots__/index.test.tsx.snap +++ b/packages/sqle/src/page/SqlManagement/__snapshots__/index.test.tsx.snap @@ -324,22 +324,17 @@ exports[`page/SqlManagement render sql management page 1`] = ` class="ant-btn css-dev-only-do-not-override-czn7jp ant-btn-default ant-btn-sm actiontech-table-actions-button switch-btn-default basic-button-wrapper css-geipcv" type="button" > - - 查看高优先级SQL - - -
-
-
- - 查看高优先级SQL - - -
-
-
- Schema +
+
+ + Schema + +
+
+ __PARSE_FAILED__ +
+
MySQL_DB_TYPE_RULE_NAME_SEPARATOR_test
@@ -2092,6 +2104,53 @@ exports[`page/SqlManagement/SQLEEIndex filter data with rule name 1`] = `
+
+
+
+ + + + + +
+
+ + 语法错误或解析器不支持 + +
+
+
+
+
- - 查看高优先级SQL - - -
-
-
- - 查看高优先级SQL - - -
-
-
- - 查看高优先级SQL - - -
-
-
- - 查看高优先级SQL - - -
-
-
{ 'filter_rule_name' ) as CustomSelectProps )?.options?.length - ).toBe(0); + ).toBe(1); + expect( + ( + result.current.filterCustomProps.get( + 'filter_rule_name' + ) as CustomSelectProps + )?.options?.[0] + ).toEqual( + expect.objectContaining({ + value: '__PARSE_FAILED__' + }) + ); expect( ( result.current.filterCustomProps.get( @@ -123,13 +134,18 @@ describe('SqlManagement/useGetTableFilterInfo', () => { const ruleFilterProps = result.current.filterCustomProps.get( 'filter_rule_name' ) as CustomSelectProps; - expect(ruleFilterProps?.options?.length).toBe(1); + expect(ruleFilterProps?.options?.length).toBe(2); expect(ruleFilterProps?.options?.[0]).toEqual( + expect.objectContaining({ + value: '__PARSE_FAILED__' + }) + ); + expect(ruleFilterProps?.options?.[1]).toEqual( expect.objectContaining({ text: expect.stringContaining('用于测试'), value: `MySQL${DB_TYPE_RULE_NAME_SEPARATOR}test` }) ); - expect(ruleFilterProps?.options?.[0]?.label).toBeDefined(); + expect(ruleFilterProps?.options?.[1]?.label).toBeDefined(); }); }); diff --git a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.test.tsx b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.test.tsx index 99460ca40c..0fff3e8db9 100644 --- a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.test.tsx +++ b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.test.tsx @@ -43,8 +43,9 @@ const exportParams = { filter_db_type: undefined, filter_rule_name: undefined, project_name: mockProjectInfo.projectName, - filter_business: undefined, filter_priority: undefined, + filter_parse_failed: undefined, + extra_filters: undefined, fuzzy_search_sql_fingerprint: '', filter_status: 'unhandled' }; @@ -202,7 +203,7 @@ describe('page/SqlManagement/SQLEEIndex', () => { const request = sqlManage.getSqlManageList(); superRender(); expect(request).toHaveBeenCalled(); - fireEvent.click(screen.getByText('查看高优先级SQL')); + fireEvent.click(screen.getByText('高优先级')); await act(async () => jest.advanceTimersByTime(3000)); expect(request).toHaveBeenCalledWith({ ...requestParams, @@ -210,19 +211,48 @@ describe('page/SqlManagement/SQLEEIndex', () => { }); }); - it('filter data with parse failed', async () => { + it('filter data with parse failed via audit rule select', async () => { const request = sqlManage.getSqlManageList(); superRender(); expect(request).toHaveBeenCalled(); expect(request).toHaveBeenCalledWith( expect.not.objectContaining({ filter_parse_failed: true }) ); - fireEvent.click(screen.getByText('语法错误或解析器不支持')); - await act(async () => jest.advanceTimersByTime(3000)); - expect(request).toHaveBeenCalledWith({ - ...requestParams, - filter_parse_failed: true + expect( + screen.queryByRole('button', { name: '语法错误或解析器不支持' }) + ).not.toBeInTheDocument(); + fireEvent.click(screen.getByText('筛选')); + expect(screen.getByText('审核规则')).toBeInTheDocument(); + const ruleSelect = screen.getByText('审核规则').closest('.ant-select')!; + fireEvent.mouseDown(ruleSelect.querySelector('.ant-select-selector')!); + await act(async () => jest.advanceTimersByTime(300)); + const parseFailedOption = Array.from( + getAllBySelector('.ant-select-item-option-content') + ).find((el) => el.textContent === '语法错误或解析器不支持'); + expect(parseFailedOption).toBeTruthy(); + await act(async () => { + fireEvent.click(parseFailedOption!); + await act(async () => jest.advanceTimersByTime(3000)); }); + await act(async () => jest.advanceTimersByTime(3000)); + expect(request).toHaveBeenCalledWith( + expect.objectContaining({ + ...requestParams, + filter_parse_failed: true, + filter_rule_name: undefined, + filter_db_type: undefined + }) + ); + expect(request).toHaveBeenCalledWith( + expect.not.objectContaining({ + filter_rule_name: '__PARSE_FAILED__' + }) + ); + expect(request).toHaveBeenCalledWith( + expect.not.objectContaining({ + filter_rule_name: '语法错误或解析器不支持' + }) + ); }); it('filter data with status', async () => { @@ -286,7 +316,7 @@ describe('page/SqlManagement/SQLEEIndex', () => { expect(request).toHaveBeenCalled(); await act(async () => jest.advanceTimersByTime(3000)); fireEvent.click(screen.getByText('与我相关')); - fireEvent.click(screen.getByText('查看高优先级SQL')); + fireEvent.click(screen.getByText('高优先级')); expect(screen.getByText('导出报表')).toBeInTheDocument(); const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });