+
+ -
+
@@ -1202,10 +1224,10 @@ LIMIT
data-testid="trigger-open-report-drawer"
>
-
-
-
- 审核通过
-
+
@@ -1407,6 +1424,11 @@ LIMIT
+
+ -
+
@@ -1414,10 +1436,10 @@ LIMIT
data-testid="trigger-open-report-drawer"
>
-
-
-
- 审核通过
-
+
@@ -1589,6 +1606,11 @@ LIMIT
+
+ -
+
@@ -1596,10 +1618,10 @@ LIMIT
data-testid="trigger-open-report-drawer"
>
-
-
-
- 审核通过
-
+
@@ -1745,6 +1762,11 @@ LIMIT
+
+ -
+
@@ -1752,32 +1774,23 @@ LIMIT
data-testid="trigger-open-report-drawer"
>
-
-
-
-
-
-
- 语法错误或者解析器不支持,请人工确认SQL正确性
-
+
+
@@ -1906,6 +1919,11 @@ LIMIT
+
+ -
+
@@ -1913,10 +1931,10 @@ LIMIT
data-testid="trigger-open-report-drawer"
>
-
-
-
- 审核通过
-
+
@@ -2105,6 +2118,11 @@ LIMIT
+
+ -
+
@@ -2112,10 +2130,10 @@ LIMIT
data-testid="trigger-open-report-drawer"
>
-
-
-
- 审核通过
-
+
@@ -2795,7 +2808,15 @@ exports[`test ScanTypeSqlCollection should open report drawer and set current au
class="ant-table-cell"
scope="col"
>
- 审核结果
+ 审核状态
+
+
+
+ 审核结果
+
+
+
+
+
+
+
+ -
+
@@ -3352,10 +3387,10 @@ LIMIT
data-testid="trigger-open-report-drawer"
>
-
-
-
- 审核通过
-
+
@@ -3557,6 +3587,11 @@ LIMIT
+
+ -
+
@@ -3564,10 +3599,10 @@ LIMIT
data-testid="trigger-open-report-drawer"
>
-
-
-
- 审核通过
-
+
@@ -3739,6 +3769,11 @@ LIMIT
+
+ -
+
@@ -3746,10 +3781,10 @@ LIMIT
data-testid="trigger-open-report-drawer"
>
-
-
-
- 审核通过
-
+
@@ -3895,6 +3925,11 @@ LIMIT
+
+ -
+
@@ -3902,32 +3937,23 @@ LIMIT
data-testid="trigger-open-report-drawer"
>
-
-
-
-
-
-
- 语法错误或者解析器不支持,请人工确认SQL正确性
-
+
+
@@ -4056,6 +4082,11 @@ LIMIT
+
+ -
+
@@ -4063,10 +4094,10 @@ LIMIT
data-testid="trigger-open-report-drawer"
>
-
-
-
- 审核通过
-
+
@@ -4255,6 +4281,11 @@ LIMIT
+
+ -
+
@@ -4262,10 +4293,10 @@ LIMIT
data-testid="trigger-open-report-drawer"
>
-
-
-
- 审核通过
-
+
diff --git a/packages/sqle/src/page/SqlManagementConf/Detail/ScanTypeSqlCollection/__tests__/index.test.tsx b/packages/sqle/src/page/SqlManagementConf/Detail/ScanTypeSqlCollection/__tests__/index.test.tsx
index a7306f3295..6fe5d7a42b 100644
--- a/packages/sqle/src/page/SqlManagementConf/Detail/ScanTypeSqlCollection/__tests__/index.test.tsx
+++ b/packages/sqle/src/page/SqlManagementConf/Detail/ScanTypeSqlCollection/__tests__/index.test.tsx
@@ -7,6 +7,8 @@ import { mockProjectInfo } from '@actiontech/shared/lib/testUtil/mockHook/data';
import { mockUseCurrentUser } from '@actiontech/shared/lib/testUtil/mockHook/mockUseCurrentUser';
import { getAllBySelector } from '@actiontech/shared/lib/testUtil/customQuery';
import rule_template from '../../../../../testUtils/mockApi/rule_template';
+import { createSpySuccessResponse } from '@actiontech/shared/lib/testUtil/mockApi';
+import { mockAuditPlanSQLData } from '../../../../../testUtils/mockApi/instanceAuditPlan/data';
describe('test ScanTypeSqlCollection', () => {
let getInstanceAuditPlanSQLMetaSpy: jest.SpyInstance;
@@ -130,4 +132,56 @@ describe('test ScanTypeSqlCollection', () => {
await act(async () => jest.advanceTimersByTime(3000));
expect(baseElement).toMatchSnapshot();
});
+
+ it('should polling request when sql audit status is being_audited', async () => {
+ getInstanceAuditPlanSQLDataSpy
+ .mockImplementationOnce(() => {
+ return createSpySuccessResponse({
+ data: {
+ rows: [
+ {
+ ...mockAuditPlanSQLData?.rows?.[0],
+ audit_results: '[]',
+ audit_status: 'being_audited'
+ }
+ ]
+ }
+ });
+ })
+ .mockImplementationOnce(() => {
+ return createSpySuccessResponse({
+ data: {
+ rows: [
+ {
+ ...mockAuditPlanSQLData?.rows?.[0]
+ }
+ ]
+ }
+ });
+ });
+
+ customRender();
+ await act(async () => jest.advanceTimersByTime(3000));
+ expect(getInstanceAuditPlanSQLDataSpy).toHaveBeenCalledTimes(1);
+ await act(async () => jest.advanceTimersByTime(3000));
+ expect(getInstanceAuditPlanSQLDataSpy).toHaveBeenCalledTimes(2);
+ });
+
+ it('should stop polling request when sql audit status is not being_audited', async () => {
+ getInstanceAuditPlanSQLDataSpy.mockImplementation(() => {
+ return createSpySuccessResponse({
+ data: {
+ rows: [
+ {
+ ...mockAuditPlanSQLData?.rows?.[0]
+ }
+ ]
+ }
+ });
+ });
+
+ customRender();
+ await act(async () => jest.advanceTimersByTime(3000));
+ expect(getInstanceAuditPlanSQLDataSpy).toHaveBeenCalledTimes(1);
+ });
});
diff --git a/packages/sqle/src/page/SqlManagementConf/Detail/ScanTypeSqlCollection/indx.tsx b/packages/sqle/src/page/SqlManagementConf/Detail/ScanTypeSqlCollection/indx.tsx
index cf12b713d4..4858255e59 100644
--- a/packages/sqle/src/page/SqlManagementConf/Detail/ScanTypeSqlCollection/indx.tsx
+++ b/packages/sqle/src/page/SqlManagementConf/Detail/ScanTypeSqlCollection/indx.tsx
@@ -8,7 +8,7 @@ import {
} from '@actiontech/shared/lib/components/ActiontechTable';
import { useTranslation } from 'react-i18next';
import ReportDrawer from '../../../../components/ReportDrawer';
-import { useCallback, useEffect, useMemo, useState } from 'react';
+import { useCallback, useEffect, useMemo, useState, ReactNode } from 'react';
import { useBoolean, useRequest } from 'ahooks';
import { ScanTypeSqlCollectionStyleWrapper } from './style';
import instance_audit_plan from '@actiontech/shared/lib/api/sqle/service/instance_audit_plan';
@@ -34,14 +34,19 @@ import {
getErrorMessage
} from '@actiontech/shared/lib/utils/Common';
import ResultIconRender from '../../../../components/AuditResultMessage/ResultIconRender';
-import AuditResultMessage from '../../../../components/AuditResultMessage';
+import AuditStatusTag from './AuditStatusTag';
+import {
+ BEING_AUDITED,
+ buildTableHeadWithAuditStatus,
+ parseAuditResult
+} from './utils';
import {
IGetInstanceAuditPlanSQLDataV1Params,
IGetInstanceAuditPlanSQLExportV1Params
} from '@actiontech/shared/lib/api/sqle/service/instance_audit_plan/index.d';
import { mergeFilterButtonMeta } from '@actiontech/shared/lib/components/ActiontechTable/hooks/useTableFilterContainer';
import { ResponseCode } from '@actiontech/shared/lib/enum';
-import { message } from 'antd';
+import { message, Tooltip } from 'antd';
import { Link } from 'react-router-dom';
const ScanTypeSqlCollection: React.FC = ({
@@ -64,6 +69,8 @@ const ScanTypeSqlCollection: React.FC = ({
const [currentAuditResultRecord, setCurrentAuditResultRecord] =
useState();
const [messageApi, messageContextHolder] = message.useMessage();
+ const [polling, { setFalse: finishPollRequest, setTrue: startPollRequest }] =
+ useBoolean();
const {
tableChange,
@@ -86,10 +93,13 @@ const ScanTypeSqlCollection: React.FC = ({
{ setTrue: openReportDrawer, setFalse: closeReportDrawer }
] = useBoolean(false);
- const onClickAuditResult = (record: ScanTypeSqlTableDataSourceItem) => {
- openReportDrawer();
- setCurrentAuditResultRecord(record);
- };
+ const onClickAuditResult = useCallback(
+ (record: ScanTypeSqlTableDataSourceItem) => {
+ openReportDrawer();
+ setCurrentAuditResultRecord(record);
+ },
+ [openReportDrawer]
+ );
const {
data: tableMetas,
@@ -165,7 +175,8 @@ const ScanTypeSqlCollection: React.FC = ({
data: tableRows,
loading: getTableRowLoading,
refresh: refreshTableRows,
- error: getTableRowError
+ error: getTableRowError,
+ cancel
} = useRequest(
() => {
const params: IGetInstanceAuditPlanSQLDataV1Params = {
@@ -186,7 +197,21 @@ const ScanTypeSqlCollection: React.FC = ({
},
{
refreshDeps: [pagination, tableFilterInfo, sortInfo],
- ready: activeTabKey === auditPlanId
+ ready: activeTabKey === auditPlanId,
+ pollingInterval: 1000,
+ pollingErrorRetryCount: 3,
+ onSuccess: (res) => {
+ if (res.data?.some((i) => i?.audit_status === BEING_AUDITED)) {
+ startPollRequest();
+ } else {
+ cancel();
+ finishPollRequest();
+ }
+ },
+ onError: () => {
+ cancel();
+ finishPollRequest();
+ }
}
);
@@ -267,6 +292,90 @@ const ScanTypeSqlCollection: React.FC = ({
};
}, [username, auditPlanType]);
+ const loading = getFilterMetaListLoading || (getTableRowLoading && !polling);
+
+ const tableHead = useMemo(
+ () =>
+ buildTableHeadWithAuditStatus(tableMetas?.head, {
+ auditStatus: t(
+ 'managementConf.detail.scanTypeSqlCollection.column.auditStatus'
+ ),
+ auditResult: t(
+ 'managementConf.detail.scanTypeSqlCollection.column.auditResult'
+ )
+ }),
+ [tableMetas?.head, t]
+ );
+
+ const columns = useMemo(() => {
+ if (!tableHead.length) {
+ return [];
+ }
+
+ const builtColumns = sortableTableColumnFactory(tableHead, {
+ columnClassName: (type) =>
+ type === 'sql' ? 'ellipsis-column-large-width' : undefined,
+ customRender: (text, record, fieldName, type) => {
+ if (fieldName === 'audit_status') {
+ return ;
+ }
+
+ if (fieldName === 'audit_results') {
+ return (
+ onClickAuditResult(record)}
+ >
+
+
+ );
+ }
+
+ if (!text) {
+ return '-';
+ }
+
+ if (type === 'time') {
+ return formatTime(text, '-');
+ }
+
+ if (type === 'sql') {
+ return (
+ onClickAuditResult(record)}
+ sql={text}
+ rows={1}
+ showCopyIcon
+ cuttingLength={200}
+ />
+ );
+ }
+
+ return text;
+ }
+ });
+
+ return builtColumns.map((column) => {
+ if (column.dataIndex === 'audit_results') {
+ return {
+ ...column,
+ title: (
+
+ {column.title as ReactNode}
+
+ )
+ };
+ }
+ return column;
+ });
+ }, [onClickAuditResult, sortableTableColumnFactory, t, tableHead]);
+
return (
@@ -284,67 +393,8 @@ const ScanTypeSqlCollection: React.FC = ({
rowKey="id"
setting={tableSetting}
errorMessage={getTableRowError && getErrorMessage(getTableRowError)}
- loading={getFilterMetaListLoading || getTableRowLoading}
- columns={sortableTableColumnFactory(tableMetas?.head ?? [], {
- columnClassName: (type) =>
- type === 'sql' ? 'ellipsis-column-large-width' : undefined,
- customRender: (text, record, fieldName, type) => {
- if (fieldName === 'audit_results') {
- let results: IAuditResult[] = [];
- try {
- results = JSON.parse(text ?? '[]') as IAuditResult[];
- } catch (error) {
- results = [];
- }
- return (
- onClickAuditResult(record)}
- >
- {results?.length > 1 ? (
-
{
- return item.level ?? '';
- })}
- />
- ) : (
-
- )}
-
- );
- }
-
- if (!text) {
- return '-';
- }
-
- if (type === 'time') {
- return formatTime(text, '-');
- }
-
- if (type === 'sql') {
- return (
- onClickAuditResult(record)}
- sql={text}
- rows={1}
- showCopyIcon
- cuttingLength={200}
- />
- );
- }
-
- return text;
- }
- })}
+ loading={loading}
+ columns={columns}
dataSource={tableRows?.data}
onChange={tableChange}
pagination={{
diff --git a/packages/sqle/src/page/SqlManagementConf/Detail/ScanTypeSqlCollection/utils.ts b/packages/sqle/src/page/SqlManagementConf/Detail/ScanTypeSqlCollection/utils.ts
new file mode 100644
index 0000000000..a4e6ef0935
--- /dev/null
+++ b/packages/sqle/src/page/SqlManagementConf/Detail/ScanTypeSqlCollection/utils.ts
@@ -0,0 +1,91 @@
+import {
+ IAuditResult,
+ IAuditPlanSQLHeadV1
+} from '@actiontech/shared/lib/api/sqle/service/common';
+import { AuditResultInfoItem } from '../../../../components/AuditResultMessage/index.type';
+
+export const BEING_AUDITED = 'being_audited';
+export const AUDITED = 'audited';
+
+const AUDIT_STATUS_FIELD = 'audit_status';
+const AUDIT_RESULTS_FIELD = 'audit_results';
+const PRIORITY_FIELD = 'priority';
+
+type AuditResultJsonItem = IAuditResult & {
+ execution_failed?: boolean;
+};
+
+export const parseAuditResult = (
+ resultString: string
+): AuditResultInfoItem[] => {
+ let results: AuditResultJsonItem[] = [];
+ try {
+ const parsed = JSON.parse(resultString ?? '[]') as
+ | AuditResultJsonItem[]
+ | null;
+ results = Array.isArray(parsed) ? parsed : [];
+ } catch {
+ results = [];
+ }
+ return results.map((item) => ({
+ level: item.level ?? '',
+ executionFailed: !!item.execution_failed
+ }));
+};
+
+export type AuditColumnLabels = {
+ auditStatus: string;
+ auditResult: string;
+};
+
+/**
+ * 在后端返回的 head 中注入 `audit_status` 列,并按以下顺序排列:
+ * ... → audit_status → priority → audit_results → ...
+ * 即:保证 `audit_status` 出现在 `priority` 之前;若 `priority` 不存在,则
+ * 退化为出现在 `audit_results` 之前。
+ *
+ * 同时统一覆盖 `audit_results` 列标题为「审核结果」,避免后端返回的旧文案。
+ */
+export const buildTableHeadWithAuditStatus = (
+ head: IAuditPlanSQLHeadV1[] | undefined,
+ labels: AuditColumnLabels
+): IAuditPlanSQLHeadV1[] => {
+ if (!head?.length) {
+ return [];
+ }
+
+ const withoutAuditStatus = head
+ .filter((item) => item.field_name !== AUDIT_STATUS_FIELD)
+ .map((item) => {
+ if (item.field_name === AUDIT_RESULTS_FIELD) {
+ return { ...item, desc: labels.auditResult };
+ }
+ return item;
+ });
+
+ const auditStatusColumn: IAuditPlanSQLHeadV1 = {
+ field_name: AUDIT_STATUS_FIELD,
+ desc: labels.auditStatus,
+ sortable: false
+ };
+
+ const priorityIndex = withoutAuditStatus.findIndex(
+ (item) => item.field_name === PRIORITY_FIELD
+ );
+ const insertIndex =
+ priorityIndex !== -1
+ ? priorityIndex
+ : withoutAuditStatus.findIndex(
+ (item) => item.field_name === AUDIT_RESULTS_FIELD
+ );
+
+ if (insertIndex === -1) {
+ return withoutAuditStatus;
+ }
+
+ return [
+ ...withoutAuditStatus.slice(0, insertIndex),
+ auditStatusColumn,
+ ...withoutAuditStatus.slice(insertIndex)
+ ];
+};