From a9654cc0714fb73bd613a677973df97c89d1dcfc Mon Sep 17 00:00:00 2001 From: yangzhongjiao Date: Fri, 10 Apr 2026 08:18:27 +0000 Subject: [PATCH 1/4] feat(api-client): add export_format param to downloadAuditTaskSQLReportV1 --- packages/shared/lib/api/sqle/service/task/index.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/shared/lib/api/sqle/service/task/index.d.ts b/packages/shared/lib/api/sqle/service/task/index.d.ts index 1504b65a3c..d42f3e6588 100644 --- a/packages/shared/lib/api/sqle/service/task/index.d.ts +++ b/packages/shared/lib/api/sqle/service/task/index.d.ts @@ -129,6 +129,8 @@ export interface IDownloadAuditTaskSQLReportV1Params { task_id: string; no_duplicate?: boolean; + + export_format?: string; } export interface IGetAuditTaskSQLsV1Params { From d539b80eb904e93046323d7b57f46175a5ca340f Mon Sep 17 00:00:00 2001 From: yangzhongjiao Date: Fri, 10 Apr 2026 08:18:27 +0000 Subject: [PATCH 2/4] feat(DownloadRecord): add multi-format export submenu with CE/EE support and i18n --- .../sqle/src/locale/en-US/execWorkflow.ts | 7 +++ .../sqle/src/locale/zh-CN/execWorkflow.ts | 7 +++ .../Common/DownloadRecord/index.tsx | 52 +++++++++++++++++-- .../Common/DownloadRecord/style.ts | 38 ++++++++++++++ 4 files changed, 100 insertions(+), 4 deletions(-) diff --git a/packages/sqle/src/locale/en-US/execWorkflow.ts b/packages/sqle/src/locale/en-US/execWorkflow.ts index 7fa06b17e8..c93e4048f7 100644 --- a/packages/sqle/src/locale/en-US/execWorkflow.ts +++ b/packages/sqle/src/locale/en-US/execWorkflow.ts @@ -262,6 +262,13 @@ export default { duplicate: 'Deduplication', downloadSql: 'Download SQL statement', downloadReport: 'Download audit report', + exportFormat: { + html: 'HTML Format', + pdf: 'PDF Format', + csv: 'CSV Format', + word: 'WORD Format (.docx)' + }, + exportFormatNotSupported: 'This export format is only available in Enterprise Edition', table: { number: 'Serial number', auditLevel: 'Rule level', diff --git a/packages/sqle/src/locale/zh-CN/execWorkflow.ts b/packages/sqle/src/locale/zh-CN/execWorkflow.ts index 336bce2c82..32896b4c71 100644 --- a/packages/sqle/src/locale/zh-CN/execWorkflow.ts +++ b/packages/sqle/src/locale/zh-CN/execWorkflow.ts @@ -300,6 +300,13 @@ export default { downloadSql: '下载SQL语句', downloadReport: '下载审核报告', downloadRollbackSql: '下载回滚语句', + exportFormat: { + html: 'HTML 格式', + pdf: 'PDF 格式', + csv: 'CSV 格式', + word: 'WORD 格式 (.docx)' + }, + exportFormatNotSupported: '该导出格式仅企业版支持', table: { number: '序号', auditLevel: '规则等级', diff --git a/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/index.tsx b/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/index.tsx index 3c134fc233..578c1bf89b 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/index.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/index.tsx @@ -15,12 +15,24 @@ import { import { CommonIconStyleWrapper } from '@actiontech/dms-kit'; // import { useCurrentProject } from '@actiontech/shared/lib/features'; +const REPORT_FORMAT_KEYS = [ + 'html', + // #if [ee] + 'pdf', + // #endif + 'csv', + // #if [ee] + 'word' + // #endif +] as const; + const DownloadRecord: React.FC = ({ noDuplicate, taskId }) => { const { t } = useTranslation(); const [open, setOpen] = useState(false); + const [reportExpanded, setReportExpanded] = useState(false); // const { projectName } = useCurrentProject(); @@ -35,17 +47,20 @@ const DownloadRecord: React.FC = ({ ); setOpen(false); }; - const downloadReport = () => { + + const downloadReport = (format: string) => { task.downloadAuditTaskSQLReportV1( { task_id: taskId, - no_duplicate: noDuplicate + no_duplicate: noDuplicate, + export_format: format }, { responseType: 'blob' } ); setOpen(false); + setReportExpanded(false); }; // todo 后端暂未实现导出回滚sql接口 暂时注释导出回滚sql相关代码 @@ -66,14 +81,38 @@ const DownloadRecord: React.FC = ({ const renderDownloadDropdown = () => { return ( -
+
setReportExpanded(!reportExpanded)} + > {t('execWorkflow.audit.downloadReport')} + + {reportExpanded ? ( + + ) : ( + + )} +
+ {reportExpanded && ( +
+ {REPORT_FORMAT_KEYS.map((formatKey) => ( +
downloadReport(formatKey)} + > + {t(`execWorkflow.audit.exportFormat.${formatKey}`)} +
+ ))} +
+ )} +
{t('execWorkflow.audit.downloadSql')} @@ -92,7 +131,12 @@ const DownloadRecord: React.FC = ({ open={open} arrow={false} trigger={['click']} - onOpenChange={setOpen} + onOpenChange={(newOpen) => { + setOpen(newOpen); + if (!newOpen) { + setReportExpanded(false); + } + }} placement="bottomLeft" content={renderDownloadDropdown()} overlayInnerStyle={{ diff --git a/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/style.ts b/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/style.ts index 7b4cb70f42..00fdc8e44f 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/style.ts +++ b/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/style.ts @@ -43,5 +43,43 @@ export const DownloadDropdownStyleWrapper = styled('div')` color: ${({ theme }) => theme.sqleTheme.execWorkflow.create.auditResult.download.itemIconColor}; } + + &-arrow { + margin-left: auto; + display: flex; + align-items: center; + } + } + + .download-record-sub-menu { + display: flex; + flex-direction: column; + align-self: stretch; + padding-left: 20px; + } + + .download-record-sub-item { + cursor: pointer; + display: flex; + height: 28px; + padding: 0 10px; + align-items: center; + align-self: stretch; + overflow: hidden; + color: ${({ theme }) => + theme.sqleTheme.execWorkflow.create.auditResult.download.itemColor}; + text-overflow: ellipsis; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 18px; + transition: background-color 0.3s; + + &:hover { + border-radius: 4px; + background: ${({ theme }) => + theme.sqleTheme.execWorkflow.create.auditResult.download + .itemHoverColor}; + } } `; From 4c6ee795b56d8c67aee81f9bba1be98ce5a7b1c1 Mon Sep 17 00:00:00 2001 From: yangzhongjiao Date: Fri, 10 Apr 2026 08:18:27 +0000 Subject: [PATCH 3/4] test(DownloadRecord): update unit tests for multi-format export submenu --- .../__snapshots__/index.ce.test.tsx.snap | 119 ----- .../__snapshots__/index.test.tsx.snap | 410 ------------------ .../__tests__/index.ce.test.tsx | 73 ++++ .../DownloadRecord/__tests__/index.test.tsx | 175 +++++++- 4 files changed, 239 insertions(+), 538 deletions(-) delete mode 100644 packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/__tests__/__snapshots__/index.ce.test.tsx.snap delete mode 100644 packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/__tests__/__snapshots__/index.test.tsx.snap diff --git a/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/__tests__/__snapshots__/index.ce.test.tsx.snap b/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/__tests__/__snapshots__/index.ce.test.tsx.snap deleted file mode 100644 index 0be0d6c8a3..0000000000 --- a/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/__tests__/__snapshots__/index.ce.test.tsx.snap +++ /dev/null @@ -1,119 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`sqle/ExecWorkflow/Common/DownloadRecord ce render snap when click down show dropdown 1`] = ` - -
- -
-
-
-
- -
-
-
- -`; diff --git a/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/__tests__/__snapshots__/index.test.tsx.snap b/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/__tests__/__snapshots__/index.test.tsx.snap deleted file mode 100644 index 7571f5d2e7..0000000000 --- a/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/__tests__/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,410 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`sqle/ExecWorkflow/Common/DownloadRecord render snap download btn 1`] = ` - -
- -
- -`; - -exports[`sqle/ExecWorkflow/Common/DownloadRecord render snap when click down file 1`] = ` - -
- -
-
-
-
- -
-
-
- -`; - -exports[`sqle/ExecWorkflow/Common/DownloadRecord render snap when click down report 1`] = ` - -
- -
-
-
-
- -
-
-
- -`; - -exports[`sqle/ExecWorkflow/Common/DownloadRecord render snap when click down show dropdown 1`] = ` - -
- -
-
-
-
- -
-
-
- -`; diff --git a/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/__tests__/index.ce.test.tsx b/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/__tests__/index.ce.test.tsx index d888b937ca..212dcbbf32 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/__tests__/index.ce.test.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/__tests__/index.ce.test.tsx @@ -3,17 +3,20 @@ */ import DownloadRecord from '..'; import { sqleSuperRender } from '../../../../../testUtils/superRender'; +import execWorkflow from '@actiontech/shared/lib/testUtil/mockApi/sqle/execWorkflow'; import { DownloadRecordProps } from '../index.type'; import { fireEvent, act, cleanup, screen } from '@testing-library/react'; import { mockUseCurrentProject } from '@actiontech/shared/lib/testUtil/mockHook/mockUseCurrentProject'; describe('sqle/ExecWorkflow/Common/DownloadRecord ce', () => { + let requestDownloadReport: jest.SpyInstance; const customRender = (params: DownloadRecordProps) => { return sqleSuperRender(); }; beforeEach(() => { jest.useFakeTimers(); + requestDownloadReport = execWorkflow.downloadAuditTaskSQLReport(); mockUseCurrentProject(); }); @@ -36,4 +39,74 @@ describe('sqle/ExecWorkflow/Common/DownloadRecord ce', () => { // expect(screen.queryByText('下载回滚语句')).not.toBeInTheDocument(); expect(baseElement).toMatchSnapshot(); }); + + it('CE version only shows HTML and CSV format options', async () => { + customRender({ + taskId: 'task Id', + noDuplicate: true + }); + + fireEvent.click(screen.getByText('下载')); + await act(async () => jest.advanceTimersByTime(300)); + + // Expand the report submenu + fireEvent.click(screen.getByText('下载审核报告')); + await act(async () => jest.advanceTimersByTime(300)); + + // CE version should only display HTML and CSV + expect(screen.getByText('HTML 格式')).toBeInTheDocument(); + expect(screen.getByText('CSV 格式')).toBeInTheDocument(); + + // CE version should NOT display PDF and WORD + expect(screen.queryByText('PDF 格式')).not.toBeInTheDocument(); + expect(screen.queryByText('WORD 格式 (.docx)')).not.toBeInTheDocument(); + }); + + it('CE version select HTML format triggers API call with export_format param', async () => { + customRender({ + taskId: 'task Id', + noDuplicate: true + }); + fireEvent.click(screen.getByText('下载')); + await act(async () => jest.advanceTimersByTime(300)); + + fireEvent.click(screen.getByText('下载审核报告')); + await act(async () => jest.advanceTimersByTime(300)); + + fireEvent.click(screen.getByText('HTML 格式')); + await act(async () => jest.advanceTimersByTime(3000)); + expect(requestDownloadReport).toHaveBeenCalled(); + expect(requestDownloadReport).toHaveBeenCalledWith( + { + task_id: 'task Id', + no_duplicate: true, + export_format: 'html' + }, + { responseType: 'blob' } + ); + }); + + it('CE version select CSV format triggers API call with export_format param', async () => { + customRender({ + taskId: 'task Id', + noDuplicate: true + }); + fireEvent.click(screen.getByText('下载')); + await act(async () => jest.advanceTimersByTime(300)); + + fireEvent.click(screen.getByText('下载审核报告')); + await act(async () => jest.advanceTimersByTime(300)); + + fireEvent.click(screen.getByText('CSV 格式')); + await act(async () => jest.advanceTimersByTime(3000)); + expect(requestDownloadReport).toHaveBeenCalled(); + expect(requestDownloadReport).toHaveBeenCalledWith( + { + task_id: 'task Id', + no_duplicate: true, + export_format: 'csv' + }, + { responseType: 'blob' } + ); + }); }); diff --git a/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/__tests__/index.test.tsx b/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/__tests__/index.test.tsx index f5023a0f8f..8ba9654e03 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/__tests__/index.test.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/__tests__/index.test.tsx @@ -4,7 +4,6 @@ import execWorkflow from '@actiontech/shared/lib/testUtil/mockApi/sqle/execWorkf import { DownloadRecordProps } from '../index.type'; import { fireEvent, act, cleanup, screen } from '@testing-library/react'; import { mockUseCurrentProject } from '@actiontech/shared/lib/testUtil/mockHook/mockUseCurrentProject'; -import { mockProjectInfo } from '@actiontech/shared/lib/testUtil/mockHook/data'; describe('sqle/ExecWorkflow/Common/DownloadRecord', () => { let requestDownloadFile: jest.SpyInstance; @@ -71,8 +70,8 @@ describe('sqle/ExecWorkflow/Common/DownloadRecord', () => { ); }); - it('render snap when click down report', async () => { - const { baseElement } = customRender({ + it('expand report submenu and show all 4 format options (EE)', async () => { + customRender({ taskId: 'task Id', noDuplicate: true }); @@ -80,41 +79,199 @@ describe('sqle/ExecWorkflow/Common/DownloadRecord', () => { await act(async () => jest.advanceTimersByTime(300)); expect(screen.getByText('下载审核报告')).toBeInTheDocument(); + // Click "下载审核报告" to expand the submenu fireEvent.click(screen.getByText('下载审核报告')); await act(async () => jest.advanceTimersByTime(300)); - expect(baseElement).toMatchSnapshot(); - await act(async () => jest.advanceTimersByTime(2800)); + + // EE version should display all 4 format options + expect(screen.getByText('HTML 格式')).toBeInTheDocument(); + expect(screen.getByText('PDF 格式')).toBeInTheDocument(); + expect(screen.getByText('CSV 格式')).toBeInTheDocument(); + expect(screen.getByText('WORD 格式 (.docx)')).toBeInTheDocument(); + }); + + it('select HTML format triggers API call with export_format param', async () => { + customRender({ + taskId: 'task Id', + noDuplicate: true + }); + fireEvent.click(screen.getByText('下载')); + await act(async () => jest.advanceTimersByTime(300)); + + // Expand the report submenu + fireEvent.click(screen.getByText('下载审核报告')); + await act(async () => jest.advanceTimersByTime(300)); + + // Click HTML format option + fireEvent.click(screen.getByText('HTML 格式')); + await act(async () => jest.advanceTimersByTime(3000)); expect(requestDownloadReport).toHaveBeenCalled(); expect(requestDownloadReport).toHaveBeenCalledWith( { task_id: 'task Id', - no_duplicate: true + no_duplicate: true, + export_format: 'html' }, { responseType: 'blob' } ); }); - it('render down report when noDuplicate is false', async () => { + it('select PDF format triggers API call with export_format param', async () => { + customRender({ + taskId: 'task Id', + noDuplicate: true + }); + fireEvent.click(screen.getByText('下载')); + await act(async () => jest.advanceTimersByTime(300)); + + fireEvent.click(screen.getByText('下载审核报告')); + await act(async () => jest.advanceTimersByTime(300)); + + fireEvent.click(screen.getByText('PDF 格式')); + await act(async () => jest.advanceTimersByTime(3000)); + expect(requestDownloadReport).toHaveBeenCalled(); + expect(requestDownloadReport).toHaveBeenCalledWith( + { + task_id: 'task Id', + no_duplicate: true, + export_format: 'pdf' + }, + { responseType: 'blob' } + ); + }); + + it('select CSV format triggers API call with export_format param', async () => { + customRender({ + taskId: 'task Id', + noDuplicate: true + }); + fireEvent.click(screen.getByText('下载')); + await act(async () => jest.advanceTimersByTime(300)); + + fireEvent.click(screen.getByText('下载审核报告')); + await act(async () => jest.advanceTimersByTime(300)); + + fireEvent.click(screen.getByText('CSV 格式')); + await act(async () => jest.advanceTimersByTime(3000)); + expect(requestDownloadReport).toHaveBeenCalled(); + expect(requestDownloadReport).toHaveBeenCalledWith( + { + task_id: 'task Id', + no_duplicate: true, + export_format: 'csv' + }, + { responseType: 'blob' } + ); + }); + + it('select WORD format triggers API call with export_format param', async () => { + customRender({ + taskId: 'task Id', + noDuplicate: true + }); + fireEvent.click(screen.getByText('下载')); + await act(async () => jest.advanceTimersByTime(300)); + + fireEvent.click(screen.getByText('下载审核报告')); + await act(async () => jest.advanceTimersByTime(300)); + + fireEvent.click(screen.getByText('WORD 格式 (.docx)')); + await act(async () => jest.advanceTimersByTime(3000)); + expect(requestDownloadReport).toHaveBeenCalled(); + expect(requestDownloadReport).toHaveBeenCalledWith( + { + task_id: 'task Id', + no_duplicate: true, + export_format: 'word' + }, + { responseType: 'blob' } + ); + }); + + it('noDuplicate false is passed correctly in format download', async () => { customRender({ taskId: 'task Id', noDuplicate: false }); fireEvent.click(screen.getByText('下载')); await act(async () => jest.advanceTimersByTime(300)); - expect(screen.getByText('下载审核报告')).toBeInTheDocument(); fireEvent.click(screen.getByText('下载审核报告')); + await act(async () => jest.advanceTimersByTime(300)); + + fireEvent.click(screen.getByText('CSV 格式')); await act(async () => jest.advanceTimersByTime(3000)); expect(requestDownloadReport).toHaveBeenCalled(); expect(requestDownloadReport).toHaveBeenCalledWith( { task_id: 'task Id', - no_duplicate: false + no_duplicate: false, + export_format: 'csv' }, { responseType: 'blob' } ); }); + it('selecting a format closes the popover and submenu', async () => { + const { baseElement } = customRender({ + taskId: 'task Id', + noDuplicate: true + }); + fireEvent.click(screen.getByText('下载')); + await act(async () => jest.advanceTimersByTime(300)); + + // Expand submenu + fireEvent.click(screen.getByText('下载审核报告')); + await act(async () => jest.advanceTimersByTime(300)); + expect(screen.getByText('HTML 格式')).toBeInTheDocument(); + + // Select a format + fireEvent.click(screen.getByText('HTML 格式')); + await act(async () => jest.advanceTimersByTime(3000)); + + // After selecting, the popover should close (format options should no longer be visible) + expect(screen.queryByText('HTML 格式')).not.toBeInTheDocument(); + }); + + it('download SQL file is not affected by report submenu changes', async () => { + customRender({ + taskId: 'task Id', + noDuplicate: true + }); + fireEvent.click(screen.getByText('下载')); + await act(async () => jest.advanceTimersByTime(300)); + + // Download SQL file directly without expanding the report submenu + fireEvent.click(screen.getByText('下载SQL语句')); + await act(async () => jest.advanceTimersByTime(3000)); + expect(requestDownloadFile).toHaveBeenCalled(); + expect(requestDownloadFile).toHaveBeenCalledWith( + { task_id: 'task Id' }, + { responseType: 'blob' } + ); + // Report API should NOT be called + expect(requestDownloadReport).not.toHaveBeenCalled(); + }); + + it('expand and collapse the report submenu', async () => { + customRender({ + taskId: 'task Id', + noDuplicate: true + }); + fireEvent.click(screen.getByText('下载')); + await act(async () => jest.advanceTimersByTime(300)); + + // Expand submenu + fireEvent.click(screen.getByText('下载审核报告')); + await act(async () => jest.advanceTimersByTime(300)); + expect(screen.getByText('HTML 格式')).toBeInTheDocument(); + + // Click again to collapse submenu + fireEvent.click(screen.getByText('下载审核报告')); + await act(async () => jest.advanceTimersByTime(300)); + expect(screen.queryByText('HTML 格式')).not.toBeInTheDocument(); + }); + // it('render snap when click down backup sql', async () => { // const { baseElement } = customRender({ // taskId: 'task Id', From 52656bfd0e70992ed3eaa1db9d290d2d698a3337 Mon Sep 17 00:00:00 2001 From: yangzhongjiao Date: Fri, 10 Apr 2026 08:18:27 +0000 Subject: [PATCH 4/4] fix(DownloadRecord): add async/await error handling, default highlight, and audit status disabled state --- .../sqle/src/locale/en-US/execWorkflow.ts | 2 + .../sqle/src/locale/zh-CN/execWorkflow.ts | 2 + .../Common/AuditResultList/index.tsx | 8 +- .../__tests__/index.ce.test.tsx | 21 +++ .../DownloadRecord/__tests__/index.test.tsx | 112 ++++++++++++++ .../Common/DownloadRecord/index.tsx | 143 +++++++++++------- .../Common/DownloadRecord/index.type.ts | 1 + .../Common/DownloadRecord/style.ts | 4 + .../SqlFileStatementOverview/index.tsx | 8 +- .../components/AuditExecResultPanel/index.tsx | 3 + 10 files changed, 246 insertions(+), 58 deletions(-) diff --git a/packages/sqle/src/locale/en-US/execWorkflow.ts b/packages/sqle/src/locale/en-US/execWorkflow.ts index c93e4048f7..3ea269bac7 100644 --- a/packages/sqle/src/locale/en-US/execWorkflow.ts +++ b/packages/sqle/src/locale/en-US/execWorkflow.ts @@ -269,6 +269,8 @@ export default { word: 'WORD Format (.docx)' }, exportFormatNotSupported: 'This export format is only available in Enterprise Edition', + downloadFailed: 'Download failed, please check your network and try again', + downloadDisabledTip: 'Please wait for the audit to complete before downloading', table: { number: 'Serial number', auditLevel: 'Rule level', diff --git a/packages/sqle/src/locale/zh-CN/execWorkflow.ts b/packages/sqle/src/locale/zh-CN/execWorkflow.ts index 32896b4c71..892897159a 100644 --- a/packages/sqle/src/locale/zh-CN/execWorkflow.ts +++ b/packages/sqle/src/locale/zh-CN/execWorkflow.ts @@ -307,6 +307,8 @@ export default { word: 'WORD 格式 (.docx)' }, exportFormatNotSupported: '该导出格式仅企业版支持', + downloadFailed: '下载失败,请检查网络后重试', + downloadDisabledTip: '请等待审核完成后再下载', table: { number: '序号', auditLevel: '规则等级', diff --git a/packages/sqle/src/page/SqlExecWorkflow/Common/AuditResultList/index.tsx b/packages/sqle/src/page/SqlExecWorkflow/Common/AuditResultList/index.tsx index 2f886048af..f589a193a2 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Common/AuditResultList/index.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Common/AuditResultList/index.tsx @@ -121,7 +121,13 @@ const AuditResultList: React.FC = ({ }} /> - + diff --git a/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/__tests__/index.ce.test.tsx b/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/__tests__/index.ce.test.tsx index 212dcbbf32..159719327b 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/__tests__/index.ce.test.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/__tests__/index.ce.test.tsx @@ -109,4 +109,25 @@ describe('sqle/ExecWorkflow/Common/DownloadRecord ce', () => { { responseType: 'blob' } ); }); + + it('first format option has default highlight style (CE: html)', async () => { + customRender({ + taskId: 'task Id', + noDuplicate: true + }); + fireEvent.click(screen.getByText('下载')); + await act(async () => jest.advanceTimersByTime(300)); + + // Expand the report submenu + fireEvent.click(screen.getByText('下载审核报告')); + await act(async () => jest.advanceTimersByTime(300)); + + // In CE, the first format is 'html' (index 0) + const firstOption = screen.getByText('HTML 格式'); + expect(firstOption.closest('.download-record-sub-item-default')).not.toBeNull(); + + // Second option (CSV) should NOT have the default class + const secondOption = screen.getByText('CSV 格式'); + expect(secondOption.closest('.download-record-sub-item-default')).toBeNull(); + }); }); diff --git a/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/__tests__/index.test.tsx b/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/__tests__/index.test.tsx index 8ba9654e03..883fac34e0 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/__tests__/index.test.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/__tests__/index.test.tsx @@ -272,6 +272,118 @@ describe('sqle/ExecWorkflow/Common/DownloadRecord', () => { expect(screen.queryByText('HTML 格式')).not.toBeInTheDocument(); }); + it('downloadReport shows error message when API call fails', async () => { + // Override mock to reject + requestDownloadReport.mockImplementation(() => + Promise.reject(new Error('Network Error')) + ); + customRender({ + taskId: 'task Id', + noDuplicate: true + }); + fireEvent.click(screen.getByText('下载')); + await act(async () => jest.advanceTimersByTime(300)); + + fireEvent.click(screen.getByText('下载审核报告')); + await act(async () => jest.advanceTimersByTime(300)); + + fireEvent.click(screen.getByText('HTML 格式')); + await act(async () => jest.advanceTimersByTime(3000)); + + // Error message should be displayed + expect( + screen.getByText('下载失败,请检查网络后重试') + ).toBeInTheDocument(); + }); + + it('downloadSql shows error message when API call fails', async () => { + // Override mock to reject + requestDownloadFile.mockImplementation(() => + Promise.reject(new Error('Network Error')) + ); + customRender({ + taskId: 'task Id', + noDuplicate: true + }); + fireEvent.click(screen.getByText('下载')); + await act(async () => jest.advanceTimersByTime(300)); + + fireEvent.click(screen.getByText('下载SQL语句')); + await act(async () => jest.advanceTimersByTime(3000)); + + // Error message should be displayed + expect( + screen.getByText('下载失败,请检查网络后重试') + ).toBeInTheDocument(); + }); + + it('first format option has default highlight style', async () => { + customRender({ + taskId: 'task Id', + noDuplicate: true + }); + fireEvent.click(screen.getByText('下载')); + await act(async () => jest.advanceTimersByTime(300)); + + // Expand the report submenu + fireEvent.click(screen.getByText('下载审核报告')); + await act(async () => jest.advanceTimersByTime(300)); + + // In EE, the first format is 'html' (index 0 of REPORT_FORMAT_KEYS) + const firstOption = screen.getByText('HTML 格式'); + expect(firstOption.closest('.download-record-sub-item-default')).not.toBeNull(); + + // Second option should NOT have the default class + const secondOption = screen.getByText('PDF 格式'); + expect(secondOption.closest('.download-record-sub-item-default')).toBeNull(); + }); + + it('download button is disabled when auditStatusFinished is false', async () => { + customRender({ + taskId: 'task Id', + noDuplicate: true, + auditStatusFinished: false + }); + const downloadBtn = screen.getByText('下载').closest('button'); + expect(downloadBtn).toBeDisabled(); + + // Clicking the disabled button should not open the popover + fireEvent.click(screen.getByText('下载')); + await act(async () => jest.advanceTimersByTime(300)); + expect(screen.queryByText('下载审核报告')).not.toBeInTheDocument(); + }); + + it('download button shows tooltip when auditStatusFinished is false', async () => { + customRender({ + taskId: 'task Id', + noDuplicate: true, + auditStatusFinished: false + }); + + // Hover over the download button to trigger tooltip + fireEvent.mouseEnter(screen.getByText('下载').closest('button')!); + await act(async () => jest.advanceTimersByTime(300)); + expect( + screen.getByText('请等待审核完成后再下载') + ).toBeInTheDocument(); + }); + + it('download button works normally when auditStatusFinished is true', async () => { + customRender({ + taskId: 'task Id', + noDuplicate: true, + auditStatusFinished: true + }); + const downloadBtn = screen.getByText('下载').closest('button'); + expect(downloadBtn).not.toBeDisabled(); + + // Clicking should open the popover + fireEvent.click(screen.getByText('下载')); + await act(async () => jest.advanceTimersByTime(300)); + expect(screen.getByText('下载审核报告')).toBeInTheDocument(); + expect(screen.getByText('下载SQL语句')).toBeInTheDocument(); + }); + // it('render snap when click down backup sql', async () => { // const { baseElement } = customRender({ // taskId: 'task Id', diff --git a/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/index.tsx b/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/index.tsx index 578c1bf89b..37dbe54fad 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/index.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/index.tsx @@ -1,4 +1,4 @@ -import { Popover, Space } from 'antd'; +import { Popover, Space, message, Tooltip } from 'antd'; import { DownloadRecordProps } from './index.type'; import { BasicButton } from '@actiontech/dms-kit'; import { useTranslation } from 'react-i18next'; @@ -28,39 +28,49 @@ const REPORT_FORMAT_KEYS = [ const DownloadRecord: React.FC = ({ noDuplicate, - taskId + taskId, + auditStatusFinished = true }) => { const { t } = useTranslation(); const [open, setOpen] = useState(false); const [reportExpanded, setReportExpanded] = useState(false); + const [messageApi, contextHolder] = message.useMessage(); // const { projectName } = useCurrentProject(); - const downloadSql = () => { - task.downloadAuditTaskSQLFileV1( - { - task_id: taskId - }, - { - responseType: 'blob' - } - ); - setOpen(false); + const downloadSql = async () => { + try { + await task.downloadAuditTaskSQLFileV1( + { + task_id: taskId + }, + { + responseType: 'blob' + } + ); + setOpen(false); + } catch { + messageApi.error(t('execWorkflow.audit.downloadFailed')); + } }; - const downloadReport = (format: string) => { - task.downloadAuditTaskSQLReportV1( - { - task_id: taskId, - no_duplicate: noDuplicate, - export_format: format - }, - { - responseType: 'blob' - } - ); - setOpen(false); - setReportExpanded(false); + const downloadReport = async (format: string) => { + try { + await task.downloadAuditTaskSQLReportV1( + { + task_id: taskId, + no_duplicate: noDuplicate, + export_format: format + }, + { + responseType: 'blob' + } + ); + setOpen(false); + setReportExpanded(false); + } catch { + messageApi.error(t('execWorkflow.audit.downloadFailed')); + } }; // todo 后端暂未实现导出回滚sql接口 暂时注释导出回滚sql相关代码 @@ -101,10 +111,10 @@ const DownloadRecord: React.FC = ({ {reportExpanded && (
- {REPORT_FORMAT_KEYS.map((formatKey) => ( + {REPORT_FORMAT_KEYS.map((formatKey, index) => (
downloadReport(formatKey)} > {t(`execWorkflow.audit.exportFormat.${formatKey}`)} @@ -126,36 +136,57 @@ const DownloadRecord: React.FC = ({ ); }; - return ( - { - setOpen(newOpen); - if (!newOpen) { - setReportExpanded(false); - } - }} - placement="bottomLeft" - content={renderDownloadDropdown()} - overlayInnerStyle={{ - padding: 0 - }} + + const downloadButton = ( + } + disabled={!auditStatusFinished} > - }> - - {t('common.download')} - - {open ? ( - - ) : ( - - )} - - - - + + {t('common.download')} + + {open ? ( + + ) : ( + + )} + + + + ); + + return ( + <> + {contextHolder} + { + if (!auditStatusFinished) { + return; + } + setOpen(newOpen); + if (!newOpen) { + setReportExpanded(false); + } + }} + placement="bottomLeft" + content={renderDownloadDropdown()} + overlayInnerStyle={{ + padding: 0 + }} + > + {auditStatusFinished ? ( + downloadButton + ) : ( + + {downloadButton} + + )} + + ); }; export default DownloadRecord; diff --git a/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/index.type.ts b/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/index.type.ts index c1add70715..7378181fbb 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/index.type.ts +++ b/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/index.type.ts @@ -2,4 +2,5 @@ export type DownloadRecordProps = { noDuplicate: boolean; taskId: string; workflowId?: string; + auditStatusFinished?: boolean; }; diff --git a/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/style.ts b/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/style.ts index 00fdc8e44f..96a8865383 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/style.ts +++ b/packages/sqle/src/page/SqlExecWorkflow/Common/DownloadRecord/style.ts @@ -82,4 +82,8 @@ export const DownloadDropdownStyleWrapper = styled('div')` .itemHoverColor}; } } + + .download-record-sub-item-default { + font-weight: 600; + } `; diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/SqlFileStatementOverview/index.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/SqlFileStatementOverview/index.tsx index 287e3a463c..e07728ef6d 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/SqlFileStatementOverview/index.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/SqlFileStatementOverview/index.tsx @@ -221,7 +221,13 @@ const SqlFileStatementOverview: React.FC = () => { > {t('execWorkflow.create.auditResult.clearDuplicate')} - + = ({ taskId={activeTabKey} noDuplicate={noDuplicate} workflowId={resetProps.workflowInfo?.workflow_id} + auditStatusFinished={ + currentTask?.status !== 'initialized' + } />