diff --git a/packages/base/src/page/Nav/SideMenu/UserMenu/Modal/VersionModal/test/__snapshots__/BasicVersionModal.test.tsx.snap b/packages/base/src/page/Nav/SideMenu/UserMenu/Modal/VersionModal/test/__snapshots__/BasicVersionModal.test.tsx.snap
index b30049a529..8e6e5a31e5 100644
--- a/packages/base/src/page/Nav/SideMenu/UserMenu/Modal/VersionModal/test/__snapshots__/BasicVersionModal.test.tsx.snap
+++ b/packages/base/src/page/Nav/SideMenu/UserMenu/Modal/VersionModal/test/__snapshots__/BasicVersionModal.test.tsx.snap
@@ -110,7 +110,7 @@ exports[`base/Nav/SideMenu/UserMenu/BasicVersionModal render snap when open is t
class="ant-typography css-dev-only-do-not-override-txh9fw"
>
UI:
- dev-zjrc-error-p0p1 93c9fa7e2
+ dev-zjrc-sql-audit-report-summary f4fab7b02
UI:
- dev-zjrc-error-p0p1 93c9fa7e2
+ dev-zjrc-sql-audit-report-summary f4fab7b02
UI:
- dev-zjrc-error-p0p1 93c9fa7e2
+ dev-zjrc-sql-audit-report-summary f4fab7b02
UI:
- dev-zjrc-error-p0p1 93c9fa7e2
+ dev-zjrc-sql-audit-report-summary f4fab7b02
UI:
- dev-zjrc-error-p0p1 93c9fa7e2
+ dev-zjrc-sql-audit-report-summary f4fab7b02
UI:
- dev-zjrc-error-p0p1 93c9fa7e2
+ dev-zjrc-sql-audit-report-summary f4fab7b02
(
+ `/v1/tasks/audits/${task_id}/audit_summary`,
+ paramsData,
+ options
+ );
+ }
+
public DownloadAuditFile(
params: IDownloadAuditFileParams,
options?: AxiosRequestConfig
diff --git a/packages/sqle/src/locale/en-US/sqlAudit.ts b/packages/sqle/src/locale/en-US/sqlAudit.ts
index bc0e371278..310e3cbe3d 100644
--- a/packages/sqle/src/locale/en-US/sqlAudit.ts
+++ b/packages/sqle/src/locale/en-US/sqlAudit.ts
@@ -42,7 +42,41 @@ export default {
download: 'Download audit result',
auditID: 'Audit ID',
auditRating: 'Audit rating',
- auditPassRate: 'Audit pass rate'
+ auditPassRate: 'Audit pass rate',
+ sourceHeader: {
+ instanceName: 'Data source',
+ address: 'Address',
+ instanceDbType: 'Instance type',
+ instanceSchema: 'Schema',
+ sqlSource: 'SQL source',
+ sourceFiles: 'Source files',
+ sqlSourceEnum: {
+ form_data: 'Form',
+ sql_file: 'SQL file',
+ mybatis_xml_file: 'MyBatis',
+ zip_file: 'ZIP',
+ git_repository: 'GIT'
+ }
+ },
+ levelHitSummary: {
+ title: 'Level hit summary',
+ sqlCount: 'SQL count',
+ loading: 'Loading…',
+ loadFailed: 'Failed to load level hit summary'
+ },
+ ruleHitDetails: {
+ title: 'Rule hit details',
+ empty: 'No rule hits',
+ loading: 'Loading…',
+ loadFailed: 'Failed to load rule hit details',
+ columns: {
+ rule: 'Rule',
+ level: 'Level',
+ hitCount: 'Hit count',
+ message: 'Description',
+ sqlNumbers: 'Related SQL'
+ }
+ }
},
create: {
title: 'Create SQL audit',
diff --git a/packages/sqle/src/locale/zh-CN/sqlAudit.ts b/packages/sqle/src/locale/zh-CN/sqlAudit.ts
index ebfeb15550..bdd8035586 100644
--- a/packages/sqle/src/locale/zh-CN/sqlAudit.ts
+++ b/packages/sqle/src/locale/zh-CN/sqlAudit.ts
@@ -42,7 +42,41 @@ export default {
download: '下载审核结果',
auditID: '审核ID',
auditRating: '审核评分',
- auditPassRate: '审核通过率'
+ auditPassRate: '审核通过率',
+ sourceHeader: {
+ instanceName: '数据源',
+ address: '地址',
+ instanceDbType: '实例类型',
+ instanceSchema: 'Schema',
+ sqlSource: 'SQL 来源',
+ sourceFiles: '来源文件',
+ sqlSourceEnum: {
+ form_data: '表单',
+ sql_file: 'SQL 文件',
+ mybatis_xml_file: 'MyBatis',
+ zip_file: 'ZIP',
+ git_repository: 'GIT'
+ }
+ },
+ levelHitSummary: {
+ title: '等级命中数',
+ sqlCount: 'SQL 条数',
+ loading: '加载中…',
+ loadFailed: '等级命中数加载失败'
+ },
+ ruleHitDetails: {
+ title: '命中规则细则',
+ empty: '暂无规则命中',
+ loading: '加载中…',
+ loadFailed: '规则命中细则加载失败',
+ columns: {
+ rule: '规则',
+ level: '等级',
+ hitCount: '命中次数',
+ message: '说明',
+ sqlNumbers: '涉及 SQL'
+ }
+ }
},
create: {
title: '创建SQL审核',
diff --git a/packages/sqle/src/page/SqlAudit/Detail/BasicInfoWrapper/index.tsx b/packages/sqle/src/page/SqlAudit/Detail/BasicInfoWrapper/index.tsx
index 1756929da7..a9bf132ff6 100644
--- a/packages/sqle/src/page/SqlAudit/Detail/BasicInfoWrapper/index.tsx
+++ b/packages/sqle/src/page/SqlAudit/Detail/BasicInfoWrapper/index.tsx
@@ -1,25 +1,122 @@
+import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { BasicInfoStyleWrapper } from './style';
import { BasicTag } from '@actiontech/shared';
import { getSQLAuditRecordsV1FilterSqlAuditStatusEnum } from '@actiontech/shared/lib/api/sqle/service/sql_audit_record/index.enum';
import SqlAuditStatusTag from '../../List/component/SqlAuditStatusTag';
-import { IAuditTaskResV1 } from '@actiontech/shared/lib/api/sqle/service/common';
+import {
+ IAuditTaskResV1,
+ ISQLAuditRecordInstance
+} from '@actiontech/shared/lib/api/sqle/service/common';
+import { AuditTaskResV1SqlSourceEnum } from '@actiontech/shared/lib/api/sqle/service/common.enum';
interface IBasicInfoWrapper {
id: string;
tags: string[];
status?: string | getSQLAuditRecordsV1FilterSqlAuditStatusEnum;
task?: IAuditTaskResV1;
+ instance?: ISQLAuditRecordInstance;
}
-const BasicInfoWrapper = ({ id, tags, status }: IBasicInfoWrapper) => {
+const EMPTY = '-';
+
+const displayText = (value?: string | null) => {
+ const trimmed = (value ?? '').trim();
+ return trimmed || EMPTY;
+};
+
+const BasicInfoWrapper = ({
+ id,
+ tags,
+ status,
+ task,
+ instance
+}: IBasicInfoWrapper) => {
const { t } = useTranslation();
+ const address = useMemo(() => {
+ const host = (instance?.db_host ?? '').trim();
+ const port = (instance?.db_port ?? '').trim();
+ if (!host || !port) {
+ return EMPTY;
+ }
+ return `${host}:${port}`;
+ }, [instance?.db_host, instance?.db_port]);
+
+ const sqlSourceLabel = useMemo(() => {
+ const source = task?.sql_source;
+ if (!source) {
+ return EMPTY;
+ }
+ const map: Partial
> = {
+ [AuditTaskResV1SqlSourceEnum.form_data]: t(
+ 'sqlAudit.detail.sourceHeader.sqlSourceEnum.form_data'
+ ),
+ [AuditTaskResV1SqlSourceEnum.sql_file]: t(
+ 'sqlAudit.detail.sourceHeader.sqlSourceEnum.sql_file'
+ ),
+ [AuditTaskResV1SqlSourceEnum.mybatis_xml_file]: t(
+ 'sqlAudit.detail.sourceHeader.sqlSourceEnum.mybatis_xml_file'
+ ),
+ [AuditTaskResV1SqlSourceEnum.zip_file]: t(
+ 'sqlAudit.detail.sourceHeader.sqlSourceEnum.zip_file'
+ ),
+ [AuditTaskResV1SqlSourceEnum.git_repository]: t(
+ 'sqlAudit.detail.sourceHeader.sqlSourceEnum.git_repository'
+ )
+ };
+ return map[source as AuditTaskResV1SqlSourceEnum] ?? EMPTY;
+ }, [t, task?.sql_source]);
+
+ const sourceFiles = useMemo(() => {
+ const names = (task?.audit_files ?? [])
+ .map((f) => (f.file_name ?? '').trim())
+ .filter(Boolean);
+ return names.length ? names.join('、') : EMPTY;
+ }, [task?.audit_files]);
+
+ const sourceFields: Array<{
+ testId: string;
+ label: string;
+ value: string;
+ }> = [
+ {
+ testId: 'sql-audit-report-header-instance_name',
+ label: t('sqlAudit.detail.sourceHeader.instanceName'),
+ value: displayText(task?.instance_name)
+ },
+ {
+ testId: 'sql-audit-report-header-address',
+ label: t('sqlAudit.detail.sourceHeader.address'),
+ value: address
+ },
+ {
+ testId: 'sql-audit-report-header-instance_db_type',
+ label: t('sqlAudit.detail.sourceHeader.instanceDbType'),
+ value: displayText(task?.instance_db_type)
+ },
+ {
+ testId: 'sql-audit-report-header-instance_schema',
+ label: t('sqlAudit.detail.sourceHeader.instanceSchema'),
+ value: displayText(task?.instance_schema)
+ },
+ {
+ testId: 'sql-audit-report-header-sql_source',
+ label: t('sqlAudit.detail.sourceHeader.sqlSource'),
+ value: sqlSourceLabel
+ },
+ {
+ testId: 'sql-audit-report-header-source_files',
+ label: t('sqlAudit.detail.sourceHeader.sourceFiles'),
+ value: sourceFiles
+ }
+ ];
+
return (
- {t('sqlAudit.detail.auditID')}: {id ?? '-'}
+ {t('sqlAudit.detail.auditID')}: {id ?? EMPTY}
@@ -29,7 +126,7 @@ const BasicInfoWrapper = ({ id, tags, status }: IBasicInfoWrapper) => {
status={status as getSQLAuditRecordsV1FilterSqlAuditStatusEnum}
/>
) : (
- '-'
+ EMPTY
)}
@@ -42,9 +139,20 @@ const BasicInfoWrapper = ({ id, tags, status }: IBasicInfoWrapper) => {
);
})
- : '-'}
+ : EMPTY}
+
+ {sourceFields.map((field) => (
+
+ {field.label}:{field.value}
+
+ ))}
+
);
};
diff --git a/packages/sqle/src/page/SqlAudit/Detail/BasicInfoWrapper/style.ts b/packages/sqle/src/page/SqlAudit/Detail/BasicInfoWrapper/style.ts
index cf8da90139..0524c78949 100644
--- a/packages/sqle/src/page/SqlAudit/Detail/BasicInfoWrapper/style.ts
+++ b/packages/sqle/src/page/SqlAudit/Detail/BasicInfoWrapper/style.ts
@@ -41,4 +41,18 @@ export const BasicInfoStyleWrapper = styled('div')`
}
}
}
+
+ .source-fields-cont {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px 16px;
+ margin-top: 12px;
+
+ .source-field-item {
+ font-size: 13px;
+ font-weight: 500;
+ line-height: 20px;
+ color: ${({ theme }) => theme.sharedTheme.uiToken.colorTextSecondary};
+ }
+ }
`;
diff --git a/packages/sqle/src/page/SqlAudit/Detail/LevelHitSummary/index.tsx b/packages/sqle/src/page/SqlAudit/Detail/LevelHitSummary/index.tsx
new file mode 100644
index 0000000000..350e06cc7a
--- /dev/null
+++ b/packages/sqle/src/page/SqlAudit/Detail/LevelHitSummary/index.tsx
@@ -0,0 +1,109 @@
+import { useTranslation } from 'react-i18next';
+import { IAuditLevelSummary } from '@actiontech/shared/lib/api/sqle/service/common';
+import { LevelHitSummaryStyleWrapper } from './style';
+
+type LevelHitSummaryProps = {
+ levelSummary?: IAuditLevelSummary | null;
+ loading?: boolean;
+ error?: boolean;
+};
+
+const toCount = (value?: number) =>
+ typeof value === 'number' && Number.isFinite(value) ? value : 0;
+
+const LevelHitSummary = ({
+ levelSummary,
+ loading,
+ error
+}: LevelHitSummaryProps) => {
+ const { t } = useTranslation();
+
+ const errorCount = toCount(levelSummary?.error_count);
+ const errorP0 = toCount(levelSummary?.error_p0_count);
+ const errorP1 = toCount(levelSummary?.error_p1_count);
+ const warnCount = toCount(levelSummary?.warn_count);
+ const noticeCount = toCount(levelSummary?.notice_count);
+ const normalCount = toCount(levelSummary?.normal_count);
+ const sqlCount = toCount(levelSummary?.sql_count);
+ const errorPriorityParts = [
+ errorP0 > 0 ? `P0 ${errorP0}` : '',
+ errorP1 > 0 ? `P1 ${errorP1}` : ''
+ ].filter(Boolean);
+ const errorPriorityText =
+ errorPriorityParts.length > 0 ? `(${errorPriorityParts.join(',')})` : '';
+
+ return (
+
+
+ {t('sqlAudit.detail.levelHitSummary.title')}
+
+ {error ? (
+
+ {t('sqlAudit.detail.levelHitSummary.loadFailed')}
+
+ ) : loading && !levelSummary ? (
+
+ {t('sqlAudit.detail.levelHitSummary.loading')}
+
+ ) : (
+
+
+ {t('components.auditResultMessage.level.error')}
+ {errorCount}
+ {errorPriorityText ? (
+
+ {errorPriorityText}
+
+ ) : null}
+
+
+ {t('components.auditResultMessage.level.warn')}
+ {warnCount}
+
+
+ {t('components.auditResultMessage.level.notice')}
+ {noticeCount}
+
+
+ {t('components.auditResultMessage.level.normal')}
+ {normalCount}
+
+
+ {t('sqlAudit.detail.levelHitSummary.sqlCount')}
+ {sqlCount}
+
+
+ )}
+
+ );
+};
+
+export default LevelHitSummary;
diff --git a/packages/sqle/src/page/SqlAudit/Detail/LevelHitSummary/style.ts b/packages/sqle/src/page/SqlAudit/Detail/LevelHitSummary/style.ts
new file mode 100644
index 0000000000..6ec5d7b4cd
--- /dev/null
+++ b/packages/sqle/src/page/SqlAudit/Detail/LevelHitSummary/style.ts
@@ -0,0 +1,51 @@
+import { styled } from '@mui/material/styles';
+
+export const LevelHitSummaryStyleWrapper = styled('div')`
+ padding: 16px 40px;
+ border-bottom: 1px solid
+ ${({ theme }) => theme.sharedTheme.uiToken.colorBorderSecondary};
+
+ .section-title {
+ font-size: 14px;
+ font-weight: 600;
+ line-height: 22px;
+ color: ${({ theme }) => theme.sharedTheme.uiToken.colorText};
+ margin-bottom: 12px;
+ }
+
+ .level-hit-items {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px 20px;
+ }
+
+ .level-hit-item {
+ font-size: 13px;
+ font-weight: 500;
+ line-height: 20px;
+ color: ${({ theme }) => theme.sharedTheme.uiToken.colorTextSecondary};
+
+ .level-hit-value {
+ font-weight: 600;
+ color: ${({ theme }) => theme.sharedTheme.uiToken.colorText};
+ margin-left: 4px;
+ }
+
+ .level-hit-sub {
+ margin-left: 6px;
+ color: ${({ theme }) => theme.sharedTheme.uiToken.colorTextSecondary};
+ }
+ }
+
+ .level-hit-error {
+ display: inline-flex;
+ align-items: baseline;
+ flex-wrap: wrap;
+ }
+
+ .level-hit-empty,
+ .level-hit-error-msg {
+ font-size: 13px;
+ color: ${({ theme }) => theme.sharedTheme.uiToken.colorTextSecondary};
+ }
+`;
diff --git a/packages/sqle/src/page/SqlAudit/Detail/RuleHitDetails/index.tsx b/packages/sqle/src/page/SqlAudit/Detail/RuleHitDetails/index.tsx
new file mode 100644
index 0000000000..300a16dd48
--- /dev/null
+++ b/packages/sqle/src/page/SqlAudit/Detail/RuleHitDetails/index.tsx
@@ -0,0 +1,98 @@
+import { useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+import { IAuditRuleHitDetail } from '@actiontech/shared/lib/api/sqle/service/common';
+import { resolveAuditLevelDisplayMeta } from '../../../../components/AuditResultMessage/errorPriorityDisplay';
+import { RuleHitDetailsStyleWrapper } from './style';
+
+type RuleHitDetailsProps = {
+ ruleHitDetails?: IAuditRuleHitDetail[] | null;
+ loading?: boolean;
+ error?: boolean;
+};
+
+const EMPTY = '-';
+
+const RuleHitDetails = ({
+ ruleHitDetails,
+ loading,
+ error
+}: RuleHitDetailsProps) => {
+ const { t } = useTranslation();
+
+ const rows = useMemo(() => ruleHitDetails ?? [], [ruleHitDetails]);
+
+ return (
+
+
+ {t('sqlAudit.detail.ruleHitDetails.title')}
+
+ {error ? (
+
+ {t('sqlAudit.detail.ruleHitDetails.loadFailed')}
+
+ ) : loading && !ruleHitDetails ? (
+
+ {t('sqlAudit.detail.ruleHitDetails.loading')}
+
+ ) : rows.length === 0 ? (
+
+ {t('sqlAudit.detail.ruleHitDetails.empty')}
+
+ ) : (
+
+
+
+ | {t('sqlAudit.detail.ruleHitDetails.columns.rule')} |
+ {t('sqlAudit.detail.ruleHitDetails.columns.level')} |
+ {t('sqlAudit.detail.ruleHitDetails.columns.hitCount')} |
+ {t('sqlAudit.detail.ruleHitDetails.columns.message')} |
+ {t('sqlAudit.detail.ruleHitDetails.columns.sqlNumbers')} |
+
+
+
+ {rows.map((item, index) => {
+ const displayName =
+ (item.message ?? '').trim() ||
+ (item.rule_name ?? '').trim() ||
+ EMPTY;
+ const meta = resolveAuditLevelDisplayMeta(
+ item.level,
+ item.error_priority
+ );
+ const levelLabel = t(meta.labelKey);
+ const hitCount =
+ typeof item.hit_count === 'number' &&
+ Number.isFinite(item.hit_count)
+ ? item.hit_count
+ : 0;
+ const sqlNumbers = (item.sql_numbers ?? [])
+ .map((n) => `#${n}`)
+ .join(', ');
+ const rowKey = `${item.rule_name ?? displayName}-${index}`;
+
+ return (
+
+ | {displayName} |
+ {levelLabel} |
+
+ {hitCount}
+ |
+ {(item.message ?? '').trim() || EMPTY} |
+ {sqlNumbers || EMPTY} |
+
+ );
+ })}
+
+
+ )}
+
+ );
+};
+
+export default RuleHitDetails;
diff --git a/packages/sqle/src/page/SqlAudit/Detail/RuleHitDetails/style.ts b/packages/sqle/src/page/SqlAudit/Detail/RuleHitDetails/style.ts
new file mode 100644
index 0000000000..bae177dd38
--- /dev/null
+++ b/packages/sqle/src/page/SqlAudit/Detail/RuleHitDetails/style.ts
@@ -0,0 +1,46 @@
+import { styled } from '@mui/material/styles';
+
+export const RuleHitDetailsStyleWrapper = styled('div')`
+ padding: 16px 40px;
+ border-bottom: 1px solid
+ ${({ theme }) => theme.sharedTheme.uiToken.colorBorderSecondary};
+
+ .section-title {
+ font-size: 14px;
+ font-weight: 600;
+ line-height: 22px;
+ color: ${({ theme }) => theme.sharedTheme.uiToken.colorText};
+ margin-bottom: 12px;
+ }
+
+ .rule-hit-table {
+ width: 100%;
+ border-collapse: collapse;
+
+ th,
+ td {
+ padding: 8px 12px;
+ text-align: left;
+ font-size: 13px;
+ line-height: 20px;
+ border-bottom: 1px solid
+ ${({ theme }) => theme.sharedTheme.uiToken.colorBorderSecondary};
+ }
+
+ th {
+ font-weight: 600;
+ color: ${({ theme }) => theme.sharedTheme.uiToken.colorTextSecondary};
+ }
+
+ td {
+ color: ${({ theme }) => theme.sharedTheme.uiToken.colorText};
+ font-weight: 500;
+ }
+ }
+
+ .rule-hit-empty,
+ .rule-hit-error-msg {
+ font-size: 13px;
+ color: ${({ theme }) => theme.sharedTheme.uiToken.colorTextSecondary};
+ }
+`;
diff --git a/packages/sqle/src/page/SqlAudit/Detail/__snapshots__/index.test.tsx.snap b/packages/sqle/src/page/SqlAudit/Detail/__snapshots__/index.test.tsx.snap
index b4d0a4d7b0..80f8e1f72b 100644
--- a/packages/sqle/src/page/SqlAudit/Detail/__snapshots__/index.test.tsx.snap
+++ b/packages/sqle/src/page/SqlAudit/Detail/__snapshots__/index.test.tsx.snap
@@ -84,7 +84,7 @@ exports[`sqle/SqlAudit/Detail should match snap shot 1`] = `
style="height: 60px;"
/>
+