diff --git a/packages/base/src/page/System/GlobalSetting/components/CBOperationLogsExpiredHours.tsx b/packages/base/src/page/System/GlobalSetting/components/CBOperationLogsExpiredHours.tsx index d5a9c0107..741742bd9 100644 --- a/packages/base/src/page/System/GlobalSetting/components/CBOperationLogsExpiredHours.tsx +++ b/packages/base/src/page/System/GlobalSetting/components/CBOperationLogsExpiredHours.tsx @@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next'; import { ConfigItem } from '@actiontech/dms-kit'; import { EditInputNumber, LabelContent } from '@actiontech/dms-kit'; import useValidatorNumber from './useValidatorNumber'; -import { IUpdateSystemVariablesReqV1 } from '@actiontech/shared/lib/api/sqle/service/common'; +import { IUpdateSystemVariablesReqV1 } from '@actiontech/shared/lib/api/base/service/common'; import { PERMISSIONS, usePermission } from '@actiontech/shared/lib/features'; export interface CBOperationLogsExpiredHoursProps { expiredHours: number | undefined; diff --git a/packages/base/src/page/System/GlobalSetting/components/OperationRecordExpiredHours.tsx b/packages/base/src/page/System/GlobalSetting/components/OperationRecordExpiredHours.tsx index 512081f4f..1fa748a78 100644 --- a/packages/base/src/page/System/GlobalSetting/components/OperationRecordExpiredHours.tsx +++ b/packages/base/src/page/System/GlobalSetting/components/OperationRecordExpiredHours.tsx @@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next'; import { ConfigItem } from '@actiontech/dms-kit'; import { EditInputNumber, LabelContent } from '@actiontech/dms-kit'; import useValidatorNumber from './useValidatorNumber'; -import { IUpdateSystemVariablesReqV1 } from '@actiontech/shared/lib/api/sqle/service/common'; +import { IUpdateSystemVariablesReqV1 } from '@actiontech/shared/lib/api/base/service/common'; import { PERMISSIONS, usePermission } from '@actiontech/shared/lib/features'; export interface OperationRecordExpiredHoursProps { expiredHours: number | undefined; diff --git a/packages/base/src/page/System/GlobalSetting/components/UrlAddressPrefixTips.tsx b/packages/base/src/page/System/GlobalSetting/components/UrlAddressPrefixTips.tsx index 35ff9521f..5dce40080 100644 --- a/packages/base/src/page/System/GlobalSetting/components/UrlAddressPrefixTips.tsx +++ b/packages/base/src/page/System/GlobalSetting/components/UrlAddressPrefixTips.tsx @@ -1,7 +1,7 @@ import { useTranslation } from 'react-i18next'; import { ConfigItem } from '@actiontech/dms-kit'; import { EditInput, LabelContent } from '@actiontech/dms-kit'; -import { IUpdateSystemVariablesReqV1 } from '@actiontech/shared/lib/api/sqle/service/common'; +import { IUpdateSystemVariablesReqV1 } from '@actiontech/shared/lib/api/base/service/common'; import { PERMISSIONS, usePermission } from '@actiontech/shared/lib/features'; export interface UrlAddressPrefixTipsProps { url: string | undefined; diff --git a/packages/base/src/page/System/GlobalSetting/index.tsx b/packages/base/src/page/System/GlobalSetting/index.tsx index 94b1d484e..0b096c3fd 100644 --- a/packages/base/src/page/System/GlobalSetting/index.tsx +++ b/packages/base/src/page/System/GlobalSetting/index.tsx @@ -4,7 +4,7 @@ import { useBoolean, useRequest } from 'ahooks'; import useHideConfigInputNode from '@actiontech/dms-kit/es/components/ConfigItem/hooks/useHideConfigInputNode'; import { DmsApi } from '@actiontech/shared/lib/api'; import { ResponseCode, ConfigFieldMapMeta } from '@actiontech/dms-kit'; -import { IUpdateSystemVariablesReqV1 } from '@actiontech/shared/lib/api/sqle/service/common'; +import { IUpdateSystemVariablesReqV1 } from '@actiontech/shared/lib/api/base/service/common'; import { Spin } from 'antd'; import SystemBasicTitle from '../components/BasicTitle'; import OperationRecordExpiredHours from './components/OperationRecordExpiredHours'; diff --git a/packages/base/src/router/test/__snapshots__/router.base.test.tsx.snap b/packages/base/src/router/test/__snapshots__/router.base.test.tsx.snap index 5577819f8..d755c2c7d 100644 --- a/packages/base/src/router/test/__snapshots__/router.base.test.tsx.snap +++ b/packages/base/src/router/test/__snapshots__/router.base.test.tsx.snap @@ -387,7 +387,7 @@ exports[`base/router-base-ee render base route data snap 1`] = ` workflowSqlFileStatementOverview , "key": "workflowSqlFileStatementOverview", - "path": ":taskId/files/:fileId/sqls", + "path": ":workflowId/task/:taskId/files/:fileId/sqls", }, ], "element":
, "key": "workflowSqlFileStatementOverview", - "path": ":taskId/files/:fileId/sqls", + "path": ":workflowId/task/:taskId/files/:fileId/sqls", }, ], "element":
{ it('render SQLFileStatementOverview', async () => { const { baseElement } = customRender([ - `/sqle/project/${projectID}/exec-workflow/123/files/434/sqls` + `/sqle/project/${projectID}/exec-workflow/789/task/123/files/434/sqls` ]); await act(async () => jest.advanceTimersByTime(300)); diff --git a/packages/base/src/router/test/router.sqle.test.tsx b/packages/base/src/router/test/router.sqle.test.tsx index 111fae236..baffe5a13 100644 --- a/packages/base/src/router/test/router.sqle.test.tsx +++ b/packages/base/src/router/test/router.sqle.test.tsx @@ -194,7 +194,7 @@ describe('base/router-sqle-ee', () => { it('render SQLFileStatementOverview', async () => { const { baseElement } = customRender([ - `/sqle/project/${projectID}/exec-workflow/123/files/434/sqls` + `/sqle/project/${projectID}/exec-workflow/789/task/123/files/434/sqls` ]); await act(async () => jest.advanceTimersByTime(300)); diff --git a/packages/dms-kit/src/data/routePaths.ts b/packages/dms-kit/src/data/routePaths.ts index 231c0d526..53c427cdb 100644 --- a/packages/dms-kit/src/data/routePaths.ts +++ b/packages/dms-kit/src/data/routePaths.ts @@ -207,7 +207,7 @@ export const ROUTE_PATHS = { }, sql_files_overview: { prefix: '/sqle/project/:projectID/exec-workflow', - path: ':taskId/files/:fileId/sqls', + path: ':workflowId/task/:taskId/files/:fileId/sqls', query: 'instance_name&schema' } }, diff --git a/packages/shared/lib/api/sqle/service/common.d.ts b/packages/shared/lib/api/sqle/service/common.d.ts index 23f03f29f..76821c29c 100644 --- a/packages/shared/lib/api/sqle/service/common.d.ts +++ b/packages/shared/lib/api/sqle/service/common.d.ts @@ -2069,14 +2069,6 @@ export interface IGetSystemModuleRedDotsRes { message?: string; } -export interface IGetSystemVariablesResV1 { - code?: number; - - data?: ISystemVariablesResV1; - - message?: string; -} - export interface IGetTableMetadataResV1 { code?: number; @@ -2740,6 +2732,8 @@ export interface IOptimizationsummary { } export interface IOptimizeSQLReq { + db_type?: string; + explain_info?: string; instance_name?: string; @@ -2813,6 +2807,10 @@ export interface IProjectScore { score?: number; } +export interface IReExecuteTaskOnWorkflowReq { + exec_sql_ids?: number[]; +} + export interface IRecordSource { name?: RecordSourceNameEnum; @@ -3445,14 +3443,6 @@ export interface IStatisticsAuditedSQLResV1 { risk_rate?: number; } -export interface ISystemVariablesResV1 { - cb_operation_logs_expired_hours?: number; - - operation_record_expired_hours?: number; - - url?: string; -} - export interface ITable { name?: string; } @@ -3853,14 +3843,6 @@ export interface IUpdateStagesInstanceDep { stage_instance_id?: string; } -export interface IUpdateSystemVariablesReqV1 { - cb_operation_logs_expired_hours?: number; - - operation_record_expired_hours?: number; - - url?: string; -} - export interface IUpdateTaskBackupStrategyReq { strategy?: UpdateTaskBackupStrategyReqStrategyEnum; } diff --git a/packages/shared/lib/api/sqle/service/configuration/index.d.ts b/packages/shared/lib/api/sqle/service/configuration/index.d.ts index 9d464d719..b289212c8 100644 --- a/packages/shared/lib/api/sqle/service/configuration/index.d.ts +++ b/packages/shared/lib/api/sqle/service/configuration/index.d.ts @@ -17,8 +17,6 @@ import { IGetLicenseResV1, ICheckLicenseResV1, ISSHPublicKeyInfoV1Rsp, - IGetSystemVariablesResV1, - IUpdateSystemVariablesReqV1, IGetWechatAuditConfigurationResV1, IUpdateWechatConfigurationReqV1, ITestWechatConfigurationReqV1, @@ -87,13 +85,6 @@ export interface IGetSSHPublicKeyReturn extends ISSHPublicKeyInfoV1Rsp {} export interface IGenSSHPublicKeyReturn extends IBaseRes {} -export interface IGetSystemVariablesV1Return extends IGetSystemVariablesResV1 {} - -export interface IUpdateSystemVariablesV1Params - extends IUpdateSystemVariablesReqV1 {} - -export interface IUpdateSystemVariablesV1Return extends IBaseRes {} - export interface IGetWechatAuditConfigurationV1Return extends IGetWechatAuditConfigurationResV1 {} diff --git a/packages/shared/lib/api/sqle/service/configuration/index.ts b/packages/shared/lib/api/sqle/service/configuration/index.ts index 5c5c7fabd..38443cc41 100644 --- a/packages/shared/lib/api/sqle/service/configuration/index.ts +++ b/packages/shared/lib/api/sqle/service/configuration/index.ts @@ -31,9 +31,6 @@ import { ICheckSQLELicenseV1Return, IGetSSHPublicKeyReturn, IGenSSHPublicKeyReturn, - IGetSystemVariablesV1Return, - IUpdateSystemVariablesV1Params, - IUpdateSystemVariablesV1Return, IGetWechatAuditConfigurationV1Return, IUpdateWechatAuditConfigurationV1Params, IUpdateWechatAuditConfigurationV1Return, @@ -233,26 +230,6 @@ class ConfigurationService extends ServiceBase { ); } - public getSystemVariablesV1(options?: AxiosRequestConfig) { - return this.get( - '/v1/configurations/system_variables', - undefined, - options - ); - } - - public updateSystemVariablesV1( - params: IUpdateSystemVariablesV1Params, - options?: AxiosRequestConfig - ) { - const paramsData = this.cloneDeep(params); - return this.patch( - '/v1/configurations/system_variables', - paramsData, - options - ); - } - public getWechatAuditConfigurationV1(options?: AxiosRequestConfig) { return this.get( '/v1/configurations/wechat_audit', diff --git a/packages/shared/lib/api/sqle/service/sql_optimization/index.d.ts b/packages/shared/lib/api/sqle/service/sql_optimization/index.d.ts index 23cd18fa9..84a813835 100644 --- a/packages/shared/lib/api/sqle/service/sql_optimization/index.d.ts +++ b/packages/shared/lib/api/sqle/service/sql_optimization/index.d.ts @@ -138,7 +138,7 @@ export interface ISQLOptimizeV2Params extends IOptimizeSQLReq { schema_name?: string; - db_type?: string; + db_type: string; optimization_name: string; diff --git a/packages/shared/lib/api/sqle/service/workflow/index.d.ts b/packages/shared/lib/api/sqle/service/workflow/index.d.ts index a9c47ac0e..d0f9c9668 100644 --- a/packages/shared/lib/api/sqle/service/workflow/index.d.ts +++ b/packages/shared/lib/api/sqle/service/workflow/index.d.ts @@ -11,6 +11,7 @@ import { IBackupSqlListRes, ICreateRollbackWorkflowReq, ICreateRollbackWorkflowRes, + IReExecuteTaskOnWorkflowReq, IGetWorkflowResV1, IUpdateWorkflowReqV1, IRejectWorkflowReqV1, @@ -215,6 +216,17 @@ export interface IGetWorkflowAttachmentParams { task_id: string; } +export interface IReExecuteTaskOnWorkflowV1Params + extends IReExecuteTaskOnWorkflowReq { + project_name: string; + + workflow_id: string; + + task_id: string; +} + +export interface IReExecuteTaskOnWorkflowV1Return extends IBaseRes {} + export interface ITerminateSingleTaskByWorkflowV1Params { workflow_id: string; diff --git a/packages/shared/lib/api/sqle/service/workflow/index.ts b/packages/shared/lib/api/sqle/service/workflow/index.ts index 3590da223..949d99d13 100644 --- a/packages/shared/lib/api/sqle/service/workflow/index.ts +++ b/packages/shared/lib/api/sqle/service/workflow/index.ts @@ -32,6 +32,8 @@ import { ITerminateMultipleTaskByWorkflowV1Params, ITerminateMultipleTaskByWorkflowV1Return, IGetWorkflowAttachmentParams, + IReExecuteTaskOnWorkflowV1Params, + IReExecuteTaskOnWorkflowV1Return, ITerminateSingleTaskByWorkflowV1Params, ITerminateSingleTaskByWorkflowV1Return, IGetWorkflowV1Params, @@ -299,6 +301,27 @@ class WorkflowService extends ServiceBase { ); } + public reExecuteTaskOnWorkflowV1( + params: IReExecuteTaskOnWorkflowV1Params, + options?: AxiosRequestConfig + ) { + const paramsData = this.cloneDeep(params); + const project_name = paramsData.project_name; + delete paramsData.project_name; + + const workflow_id = paramsData.workflow_id; + delete paramsData.workflow_id; + + const task_id = paramsData.task_id; + delete paramsData.task_id; + + return this.post( + `/v1/projects/${project_name}/workflows/${workflow_id}/tasks/${task_id}/re_execute`, + paramsData, + options + ); + } + public terminateSingleTaskByWorkflowV1( params: ITerminateSingleTaskByWorkflowV1Params, options?: AxiosRequestConfig diff --git a/packages/shared/lib/testUtil/mockApi/sqle/execWorkflow/index.ts b/packages/shared/lib/testUtil/mockApi/sqle/execWorkflow/index.ts index a00b8efe5..6ae23cd8f 100644 --- a/packages/shared/lib/testUtil/mockApi/sqle/execWorkflow/index.ts +++ b/packages/shared/lib/testUtil/mockApi/sqle/execWorkflow/index.ts @@ -47,6 +47,7 @@ class MockWorkflowApi implements MockSpyApy { this.createRollbackWorkflow(); this.updateTaskBackupStrategy(); this.getBackupSqlList(); + this.reExecuteTaskOnWorkflow(); } public getWorkflows() { @@ -349,6 +350,12 @@ VALUES ('1234567890', 'example@email.com', '123456789012345678', '9876543210', ' ); return spy; } + + public reExecuteTaskOnWorkflow() { + const spy = jest.spyOn(workflow, 'reExecuteTaskOnWorkflowV1'); + spy.mockImplementation(() => createSpySuccessResponse({})); + return spy; + } } export default new MockWorkflowApi(); diff --git a/packages/sqle/src/data/EmitterKey.ts b/packages/sqle/src/data/EmitterKey.ts index 588787c75..616503426 100644 --- a/packages/sqle/src/data/EmitterKey.ts +++ b/packages/sqle/src/data/EmitterKey.ts @@ -41,7 +41,9 @@ enum EmitterKey { Refresh_Sql_Exec_workflow_Audit_Result_List = 'Refresh_Sql_Exec_workflow_Audit_Result_List', Refresh_Sql_Audit_List = 'Refresh_Sql_Audit_List', - Refresh_Sql_Optimization_List = 'Refresh_Sql_Optimization_List' + Refresh_Sql_Optimization_List = 'Refresh_Sql_Optimization_List', + + Sql_Retry_Execute_Done = 'Sql_Retry_Execute_Done' } export default EmitterKey; diff --git a/packages/sqle/src/data/ModalName.ts b/packages/sqle/src/data/ModalName.ts index e92d42445..8e15f0259 100644 --- a/packages/sqle/src/data/ModalName.ts +++ b/packages/sqle/src/data/ModalName.ts @@ -45,5 +45,8 @@ export enum ModalName { Sql_Optimization_Result_Modal = 'SQL_OPTIMIZATION_RESULT_MODAL', Sql_Optimization_Query_Plan_Flow_Modal = 'SQL_OPTIMIZATION_QUERY_PLAN_FLOW_MODAL', Sql_Optimization_Query_Plan_Diff_Modal = 'SQL_OPTIMIZATION_QUERY_PLAN_DIFF_MODAL', - Sql_Optimization_Result_Drawer = 'SQL_OPTIMIZATION_RESULT_DRAWER' + Sql_Optimization_Result_Drawer = 'SQL_OPTIMIZATION_RESULT_DRAWER', + + // sql exec workflow + Sql_Exec_Workflow_Retry_Execute_Modal = 'SQL_EXEC_WORKFLOW_RETRY_EXECUTE_MODAL' } diff --git a/packages/sqle/src/locale/zh-CN/execWorkflow.ts b/packages/sqle/src/locale/zh-CN/execWorkflow.ts index f7ca11a3d..c1dc215b8 100644 --- a/packages/sqle/src/locale/zh-CN/execWorkflow.ts +++ b/packages/sqle/src/locale/zh-CN/execWorkflow.ts @@ -259,7 +259,12 @@ export default { cancelExecScheduled: '取消定时上线', cancelExecScheduledTips: '取消定时上线成功', sqlExecuteConfirmTips: - '当前操作将立即执行该数据源上的SQL语句, 是否确认立即上线' + '当前操作将立即执行该数据源上的SQL语句, 是否确认立即上线', + retryExecute: '再次执行', + retryExecuteSuccess: '再次执行成功', + pleaseSelectSql: '请选择需要再次执行的SQL', + selectRetryExecuteSqlDesc: '已执行成功的SQL无法再次执行', + selectRetryExecuteSql: '选择再次执行的SQL' } }, taskResult: {}, diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/__tests__/__snapshots__/index.test.tsx.snap b/packages/sqle/src/page/SqlExecWorkflow/Detail/__tests__/__snapshots__/index.test.tsx.snap index 0b4d377b3..451a3798b 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/__tests__/__snapshots__/index.test.tsx.snap +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/__tests__/__snapshots__/index.test.tsx.snap @@ -672,6 +672,20 @@ exports[`sqle/ExecWorkflow/Detail render current workflow status is wait for exe
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
{ return { @@ -89,9 +90,23 @@ describe('sqle/ExecWorkflow/Detail', () => { ); getAuditTaskSpy = task.getAuditTask(); useParamsMock.mockReturnValue({ workflowId: 'workflowId' }); - mockUsePermission(undefined, { - mockSelector: true - }); + (useSelector as jest.Mock).mockImplementation((selector) => + selector({ + permission: { + moduleFeatureSupport: { + sqlOptimization: false, + knowledge: false + }, + userOperationPermissions: null + }, + sqlExecWorkflow: { + retryExecuteData: {}, + modalStatus: { + [ModalName.Sql_Exec_Workflow_Retry_Execute_Modal]: false + } + } + }) + ); }); afterEach(() => { diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/OverviewList/__tests__/__snapshots__/index.test.tsx.snap b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/OverviewList/__tests__/__snapshots__/index.test.tsx.snap index 7faf529ac..73a9b46e7 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/OverviewList/__tests__/__snapshots__/index.test.tsx.snap +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/OverviewList/__tests__/__snapshots__/index.test.tsx.snap @@ -316,6 +316,20 @@ exports[`test OverviewList matches the snapshot and displays loading state 1`] =
+
+ +
+
+ +
+
+ +
+
+ +
({ ...jest.requireActual('react-redux'), - useSelector: jest.fn() + useSelector: jest.fn(), + useDispatch: jest.fn() })); describe('test OverviewList', () => { @@ -35,6 +40,7 @@ describe('test OverviewList', () => { let executeOneTaskOnWorkflowSpy: jest.SpyInstance; let terminateSingleTaskByWorkflowSpy: jest.SpyInstance; let updateWorkflowScheduleSpy: jest.SpyInstance; + const dispatchSpy = jest.fn(); ignoreConsoleErrors([UtilsConsoleErrorStringsEnum.INVALID_CUSTOM_ATTRIBUTE]); @@ -71,6 +77,7 @@ describe('test OverviewList', () => { mockUsePermission(undefined, { mockSelector: true }); + (useDispatch as jest.Mock).mockImplementation(() => dispatchSpy); }); afterEach(() => { @@ -637,4 +644,156 @@ describe('test OverviewList', () => { expect(screen.queryByText('定时上线')).not.toBeVisible(); expect(screen.queryByText('取消定时上线')).not.toBeVisible(); }); + + it('render the retry execute button and allows retry execute when the task status is exec_failed, the current user is authorized, and outside maintenance window', async () => { + mockUseCurrentUser({ username: 'test_user' }); + customRender({ + workflowInfo: { + ...WorkflowsOverviewListData, + record: { + ...WorkflowsOverviewListData.record, + workflow_step_list: [ + { + workflow_step_id: 24, + number: 3, + type: WorkflowStepResV2TypeEnum.sql_execute, + assignee_user_name_list: ['test_user'], + state: WorkflowStepResV2StateEnum.approved + } + ] + } + }, + overviewList: { + list: [ + { + ...WorkflowTasksItemData[0], + status: GetWorkflowTasksItemV2StatusEnum.exec_failed, + instance_maintenance_times: [ + { + maintenance_start_time: { hour: 9, minute: 0 }, + maintenance_stop_time: { hour: 20, minute: 0 } + } + ] + } + ], + total: 1 + } + }); + + expect(screen.getByText('再次执行').closest('button')).not.toBeDisabled(); + fireEvent.click(screen.getByText('再次执行')); + + await act(async () => jest.advanceTimersByTime(0)); + expect(dispatchSpy).toHaveBeenCalledTimes(2); + expect(dispatchSpy).toHaveBeenNthCalledWith(1, { + type: 'sqlExecWorkflow/updateRetryExecuteData', + payload: { + taskId: WorkflowTasksItemData[0].task_id?.toString() + } + }); + expect(dispatchSpy).toHaveBeenNthCalledWith(2, { + type: 'sqlExecWorkflow/updateModalStatus', + payload: { + modalName: ModalName.Sql_Exec_Workflow_Retry_Execute_Modal, + status: true + } + }); + }); + + it('render the retry execute button but disabled when the task status is exec_failed but the current user is not authorized or during maintenance window', async () => { + mockUseCurrentUser({ username: 'test_user' }); + customRender({ + overviewList: { + list: [ + { + ...WorkflowTasksItemData[0], + current_step_assignee_user_name_list: [''], + status: GetWorkflowTasksItemV2StatusEnum.wait_for_execution, + instance_maintenance_times: [ + { + maintenance_start_time: { hour: 9, minute: 0 }, + maintenance_stop_time: { hour: 20, minute: 0 } + } + ] + } + ], + total: 1 + } + }); + expect(screen.getByText('再次执行').closest('button')).not.toBeVisible(); + + cleanup(); + + // user not in the assignee_user_name_list + customRender({ + workflowInfo: { + ...WorkflowsOverviewListData, + record: { + ...WorkflowsOverviewListData.record, + workflow_step_list: [ + { + workflow_step_id: 24, + number: 3, + type: WorkflowStepResV2TypeEnum.sql_execute, + assignee_user_name_list: ['admin'], + state: WorkflowStepResV2StateEnum.approved + } + ] + } + }, + overviewList: { + list: [ + { + ...WorkflowTasksItemData[0], + status: GetWorkflowTasksItemV2StatusEnum.exec_failed, + instance_maintenance_times: [ + { + maintenance_start_time: { hour: 9, minute: 0 }, + maintenance_stop_time: { hour: 20, minute: 0 } + } + ] + } + ], + total: 1 + } + }); + expect(screen.getByText('再次执行').closest('button')).toBeDisabled(); + + cleanup(); + + // current time not allow execute workflow + customRender({ + workflowInfo: { + ...WorkflowsOverviewListData, + record: { + ...WorkflowsOverviewListData.record, + workflow_step_list: [ + { + workflow_step_id: 24, + number: 3, + type: WorkflowStepResV2TypeEnum.sql_execute, + assignee_user_name_list: ['test_user'], + state: WorkflowStepResV2StateEnum.approved + } + ] + } + }, + overviewList: { + list: [ + { + ...WorkflowTasksItemData[0], + status: GetWorkflowTasksItemV2StatusEnum.wait_for_audit, + instance_maintenance_times: [ + { + maintenance_start_time: { hour: 1, minute: 0 }, + maintenance_stop_time: { hour: 3, minute: 0 } + } + ] + } + ], + total: 1 + } + }); + expect(screen.getByText('再次执行').closest('button')).toBeDisabled(); + }); }); diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/OverviewList/action.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/OverviewList/action.tsx index 830551b2f..6068f9fcb 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/OverviewList/action.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/OverviewList/action.tsx @@ -5,7 +5,8 @@ import { import { WorkflowRecordResV2StatusEnum, UpdateWorkflowScheduleReqV2NotifyTypeEnum, - GetWorkflowTasksItemV2StatusEnum + GetWorkflowTasksItemV2StatusEnum, + WorkflowStepResV2TypeEnum } from '@actiontech/shared/lib/api/sqle/service/common.enum'; import { ActiontechTableActionsWithPermissions, @@ -14,6 +15,7 @@ import { import dayjs from 'dayjs'; import { checkTimeInWithMaintenanceTime } from '../../../../Common/utils'; import { t } from '../../../../../../locale'; +import { IWorkflowRecordResV2 } from '@actiontech/shared/lib/api/sqle/service/common'; type Params = { currentUsername: string; @@ -28,6 +30,8 @@ type Params = { taskId?: string ) => Promise; executable: boolean; + onRetryExecute: (record: IGetWorkflowTasksItemV2) => void; + workflowInfoRecord?: IWorkflowRecordResV2; }; export const AuditResultOverviewListAction = ( @@ -40,7 +44,8 @@ export const AuditResultOverviewListAction = ( workflowStatus, openScheduleModalAndSetCurrentTask, scheduleTimeHandle, - executable + executable, + workflowInfoRecord } = params; const unusableStatus = [ @@ -74,6 +79,24 @@ export const AuditResultOverviewListAction = ( return status === GetWorkflowTasksItemV2StatusEnum.wait_for_execution; }; + const enableSqlRetryExecute = ( + maintenanceTime: IMaintenanceTimeResV1[] = [] + ) => { + if ( + !workflowInfoRecord?.workflow_step_list + ?.find((v) => v.type === WorkflowStepResV2TypeEnum.sql_execute) + ?.assignee_user_name_list?.includes(currentUsername) + ) { + return false; + } + + if (maintenanceTime.length) { + return checkTimeInWithMaintenanceTime(dayjs(), maintenanceTime); + } + + return true; + }; + const enableSqlScheduleTime = ( currentStepAssigneeUsernameList: string[] = [], status?: GetWorkflowTasksItemV2StatusEnum @@ -110,6 +133,21 @@ export const AuditResultOverviewListAction = ( return { width: 180, buttons: [ + { + key: 'retryExecute', + text: t('execWorkflow.detail.overview.table.retryExecute'), + buttonProps(record) { + return { + disabled: !enableSqlRetryExecute( + record?.instance_maintenance_times + ), + hidden: + record?.status !== GetWorkflowTasksItemV2StatusEnum.exec_failed, + onClick: () => params.onRetryExecute(record!) + }; + }, + permissions: PERMISSIONS.ACTIONS.SQLE.SQL_EXEC_WORKFLOW.EXEC_TASK + }, { key: 'terminate', text: t('execWorkflow.detail.operator.terminate'), diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/OverviewList/index.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/OverviewList/index.tsx index 5fdd06fbb..0a49816f2 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/OverviewList/index.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/OverviewList/index.tsx @@ -9,6 +9,13 @@ import { ActiontechTable } from '@actiontech/dms-kit/es/components/ActiontechTab import { auditResultOverviewColumn } from './column'; import { WorkflowOverviewListProps } from './index.type'; import { AuditResultOverviewListAction } from './action'; +import { + updateRetryExecuteData, + updateSqlExecWorkflowModalStatus +} from '../../../../../../store/sqlExecWorkflow/index'; +import { ModalName } from '../../../../../../data/ModalName'; +import { useDispatch } from 'react-redux'; +import { IGetWorkflowTasksItemV2 } from '@actiontech/shared/lib/api/sqle/service/common'; const WorkflowOverviewList: React.FC = ({ workflowInfo, @@ -22,6 +29,7 @@ const WorkflowOverviewList: React.FC = ({ const { username } = useCurrentUser(); const { projectName } = useCurrentProject(); const { parse2TableActionPermissions } = usePermission(); + const dispatch = useDispatch(); const { contextHolder, scheduleModalVisible, @@ -38,6 +46,20 @@ const WorkflowOverviewList: React.FC = ({ refreshOverview: refreshOverviewAction }); + const onRetryExecute = (record: IGetWorkflowTasksItemV2) => { + dispatch( + updateRetryExecuteData({ + taskId: record.task_id?.toString() ?? '' + }) + ); + dispatch( + updateSqlExecWorkflowModalStatus({ + modalName: ModalName.Sql_Exec_Workflow_Retry_Execute_Modal, + status: true + }) + ); + }; + return ( <> {contextHolder} @@ -63,7 +85,9 @@ const WorkflowOverviewList: React.FC = ({ openScheduleModalAndSetCurrentTask, currentUsername: username, workflowStatus: workflowInfo?.record?.status, - executable: !!workflowInfo?.record?.executable + executable: !!workflowInfo?.record?.executable, + onRetryExecute, + workflowInfoRecord: workflowInfo?.record }) )} onRow={(record) => { diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/OverviewList/index.type.ts b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/OverviewList/index.type.ts index 9454f1a8d..afbe12957 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/OverviewList/index.type.ts +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/OverviewList/index.type.ts @@ -2,7 +2,7 @@ import { AuditExecResultPanelProps } from '../index.type'; export type WorkflowOverviewListProps = Omit< AuditExecResultPanelProps, - 'taskInfos' | 'activeTabKey' + 'taskInfos' | 'activeTabKey' | 'refreshTaskWithTaskId' >; export type WorkflowOverviewListActionsParams = { diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/RetryExecuteModal/__tests__/__snapshots__/index.test.tsx.snap b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/RetryExecuteModal/__tests__/__snapshots__/index.test.tsx.snap new file mode 100644 index 000000000..1d88325d3 --- /dev/null +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/RetryExecuteModal/__tests__/__snapshots__/index.test.tsx.snap @@ -0,0 +1,495 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RetryExecuteModal handles row selection correctly 1`] = ` + +
+
+
+
+
+ +
+ +`; diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/RetryExecuteModal/__tests__/index.test.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/RetryExecuteModal/__tests__/index.test.tsx new file mode 100644 index 000000000..b4577d5c5 --- /dev/null +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/RetryExecuteModal/__tests__/index.test.tsx @@ -0,0 +1,222 @@ +import { fireEvent, screen } from '@testing-library/react'; +import { act } from '@testing-library/react'; +import { sqleSuperRender } from '../../../../../../../testUtils/superRender'; +import { mockUseCurrentProject } from '@actiontech/shared/lib/testUtil/mockHook/mockUseCurrentProject'; +import { mockUseCurrentUser } from '@actiontech/shared/lib/testUtil/mockHook/mockUseCurrentUser'; +import { mockUsePermission } from '@actiontech/shared/lib/testUtil/mockHook/mockUsePermission'; +import { mockProjectInfo } from '@actiontech/shared/lib/testUtil/mockHook/data'; +import RetryExecuteModal from '../index'; +import execWorkflow from '@actiontech/shared/lib/testUtil/mockApi/sqle/execWorkflow'; +import { createSpySuccessResponse } from '@actiontech/shared/lib/testUtil'; +import { useSelector, useDispatch } from 'react-redux'; +import { useParams } from 'react-router-dom'; +import { ModalName } from '../../../../../../../data/ModalName'; +import EmitterKey from '../../../../../../../data/EmitterKey'; +import EventEmitter from '../../../../../../../utils/EventEmitter'; + +jest.mock('react-router-dom', () => { + return { + ...jest.requireActual('react-router-dom'), + useParams: jest.fn() + }; +}); + +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useSelector: jest.fn(), + useDispatch: jest.fn() +})); + +describe('RetryExecuteModal', () => { + let getAuditTaskSQLsSpy: jest.SpyInstance; + let reExecuteTaskOnWorkflowSpy: jest.SpyInstance; + const useParamsMock: jest.Mock = useParams as jest.Mock; + const dispatchSpy = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + jest.useFakeTimers(); + mockUseCurrentProject(); + mockUseCurrentUser(); + mockUsePermission(undefined, { mockSelector: true }); + + getAuditTaskSQLsSpy = execWorkflow.getAuditTaskSQLs(); + getAuditTaskSQLsSpy.mockImplementation(() => + createSpySuccessResponse({ + data: [ + { + number: 1, + exec_sql: 'SELECT * ', + sql_source_file: '', + audit_result: null, + audit_level: '', + audit_status: 'finished', + exec_result: '', + exec_status: 'initialized', + description: '', + exec_sql_id: 1 + } + ], + total_nums: 1 + }) + ); + reExecuteTaskOnWorkflowSpy = execWorkflow.reExecuteTaskOnWorkflow(); + useParamsMock.mockReturnValue({ workflowId: '456' }); + + (useSelector as jest.Mock).mockImplementation((selector) => { + return selector({ + sqlExecWorkflow: { + retryExecuteData: { + taskId: '123', + // execSqlId: 1, + pageIndex: 1, + pageSize: 20 + }, + modalStatus: { + [ModalName.Sql_Exec_Workflow_Retry_Execute_Modal]: true + } + } + }); + }); + (useDispatch as jest.Mock).mockImplementation(() => dispatchSpy); + }); + + afterEach(() => { + jest.useRealTimers(); + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + + it('handles row selection correctly', async () => { + const eventEmitterSpy = jest.spyOn(EventEmitter, 'emit'); + const { baseElement } = sqleSuperRender(); + + await act(async () => jest.advanceTimersByTime(3000)); + + expect(getAuditTaskSQLsSpy).toHaveBeenCalledWith({ + page_index: '1', + page_size: '20', + task_id: '123' + }); + expect(baseElement).toMatchSnapshot(); + + const checkboxes = screen.getAllByRole('checkbox'); + fireEvent.click(checkboxes[0]); + await act(async () => jest.advanceTimersByTime(0)); + fireEvent.click(screen.getByText('提 交')); + await act(async () => jest.advanceTimersByTime(0)); + expect(reExecuteTaskOnWorkflowSpy).toHaveBeenCalledTimes(1); + expect(reExecuteTaskOnWorkflowSpy).toHaveBeenCalledWith({ + project_name: mockProjectInfo.projectName, + workflow_id: '456', + task_id: '123', + exec_sql_ids: [1] + }); + + await act(async () => jest.advanceTimersByTime(3000)); + + expect(dispatchSpy).toHaveBeenCalledTimes(1); + expect(dispatchSpy).toHaveBeenCalledWith({ + type: 'sqlExecWorkflow/updateModalStatus', + payload: { + modalName: ModalName.Sql_Exec_Workflow_Retry_Execute_Modal, + status: false + } + }); + + expect(eventEmitterSpy).toHaveBeenCalledTimes(1); + expect(eventEmitterSpy).toHaveBeenCalledWith( + EmitterKey.Sql_Retry_Execute_Done, + '123' + ); + eventEmitterSpy.mockRestore(); + }); + + it('shows error message when no SQLs are selected', async () => { + (useSelector as jest.Mock).mockImplementation((selector) => { + return selector({ + sqlExecWorkflow: { + retryExecuteData: { + taskId: '123' + }, + modalStatus: { + [ModalName.Sql_Exec_Workflow_Retry_Execute_Modal]: true + } + } + }); + }); + sqleSuperRender(); + + await act(async () => jest.advanceTimersByTime(3000)); + + fireEvent.click(screen.getByText('提 交')); + + expect(screen.getByText('请选择需要再次执行的SQL')).toBeInTheDocument(); + }); + + it('should checked when execSqlId is exists', async () => { + (useSelector as jest.Mock).mockImplementation((selector) => { + return selector({ + sqlExecWorkflow: { + retryExecuteData: { + taskId: '123', + execSqlId: 1 + }, + modalStatus: { + [ModalName.Sql_Exec_Workflow_Retry_Execute_Modal]: true + } + } + }); + }); + sqleSuperRender(); + + await act(async () => jest.advanceTimersByTime(3000)); + const checkboxes = screen.getAllByRole('checkbox'); + expect(checkboxes[0]).toBeChecked(); + }); + + it('closes modal and resets state when cancel is clicked', async () => { + sqleSuperRender(); + + await act(async () => jest.advanceTimersByTime(3000)); + + fireEvent.click(screen.getByText('取 消')); + + expect(dispatchSpy).toHaveBeenCalledTimes(1); + expect(dispatchSpy).toHaveBeenCalledWith({ + type: 'sqlExecWorkflow/updateModalStatus', + payload: { + modalName: ModalName.Sql_Exec_Workflow_Retry_Execute_Modal, + status: false + } + }); + }); + + it('succeeded sql should be disabled', async () => { + getAuditTaskSQLsSpy.mockImplementation(() => + createSpySuccessResponse({ + data: [ + { + number: 1, + exec_sql: 'SELECT * ', + sql_source_file: '', + audit_result: null, + audit_level: '', + audit_status: 'finished', + exec_result: '', + exec_status: 'succeeded', + description: '', + exec_sql_id: 1 + } + ], + total_nums: 1 + }) + ); + sqleSuperRender(); + + await act(async () => jest.advanceTimersByTime(3000)); + + const checkboxes = screen.getAllByRole('checkbox'); + expect(checkboxes[0]).toBeDisabled(); + }); +}); diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/RetryExecuteModal/index.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/RetryExecuteModal/index.tsx new file mode 100644 index 000000000..179e7b293 --- /dev/null +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/RetryExecuteModal/index.tsx @@ -0,0 +1,277 @@ +import { + BasicTable, + useTableRequestParams, + ResponseCode, + BasicButton, + BasicTypographyEllipsis, + ROUTE_PATHS +} from '@actiontech/dms-kit'; +import { useTranslation } from 'react-i18next'; +import { useRequest } from 'ahooks'; +import task from '@actiontech/shared/lib/api/sqle/service/task'; +import workflow from '@actiontech/shared/lib/api/sqle/service/workflow'; +import { getAuditTaskSQLsV2FilterExecStatusEnum } from '@actiontech/shared/lib/api/sqle/service/task/index.enum'; +import { IAuditTaskSQLResV2 } from '@actiontech/shared/lib/api/sqle/service/common'; +import { IGetAuditTaskSQLsV2Params } from '@actiontech/shared/lib/api/sqle/service/task/index.d'; +import { useEffect, useState } from 'react'; +import { SQLRenderer, useTypedParams } from '@actiontech/shared'; +import { Space, message } from 'antd'; +import { useCurrentProject } from '@actiontech/shared/lib/features'; +import { + RetryExecuteModalStyleWrapper, + RetryExecuteModalTitleDescStyleWrapper +} from './style'; +import ExecStatusTag from '../TaskResultList/Common/ResultCard/components/ExecStatusTag'; +import { useSelector, useDispatch } from 'react-redux'; +import { IReduxState } from '../../../../../../store'; +import { ModalName } from '../../../../../../data/ModalName'; +import { updateSqlExecWorkflowModalStatus } from '../../../../../../store/sqlExecWorkflow/index'; +import EmitterKey from '../../../../../../data/EmitterKey'; +import EventEmitter from '../../../../../../utils/EventEmitter'; + +const RetryExecuteModal = () => { + const { t } = useTranslation(); + const dispatch = useDispatch(); + const urlParams = + useTypedParams(); + + const { taskId, execSqlId, pageIndex, pageSize, visible } = useSelector( + (state: IReduxState) => ({ + taskId: state.sqlExecWorkflow.retryExecuteData?.taskId ?? '', + execSqlId: state.sqlExecWorkflow.retryExecuteData?.execSqlId, + pageIndex: state.sqlExecWorkflow.retryExecuteData?.pageIndex, + pageSize: state.sqlExecWorkflow.retryExecuteData?.pageSize, + visible: + !!state.sqlExecWorkflow.modalStatus[ + ModalName.Sql_Exec_Workflow_Retry_Execute_Modal + ] + }) + ); + + const { projectName } = useCurrentProject(); + + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + const [allSelectedKeys, setAllSelectedKeys] = useState([]); + + const [messageApi, contextHolder] = message.useMessage(); + + const { tableChange, pagination } = useTableRequestParams< + IAuditTaskSQLResV2, + IGetAuditTaskSQLsV2Params + >(); + + const { data, loading } = useRequest( + () => { + return task + .getAuditTaskSQLsV2({ + page_index: `${pagination.page_index}`, + page_size: `${pagination.page_size}`, + task_id: taskId + }) + .then((res) => { + if (res.data.code === ResponseCode.SUCCESS) { + return res.data; + } + }); + }, + { + ready: visible && !!taskId, + refreshDeps: [taskId, pagination], + onSuccess: (result) => { + const currentPageKeys = + result?.data?.map((item) => item.exec_sql_id) || []; + const currentPageSelectedKeys = allSelectedKeys.filter((key) => + currentPageKeys.includes(Number(key)) + ); + setSelectedRowKeys(currentPageSelectedKeys); + } + } + ); + + const { run: retryExecute, loading: retryExecuteLoading } = useRequest( + () => { + return workflow + .reExecuteTaskOnWorkflowV1({ + project_name: projectName, + workflow_id: urlParams.workflowId ?? '', + task_id: taskId, + exec_sql_ids: allSelectedKeys as number[] + }) + .then((res) => { + if (res.data.code === ResponseCode.SUCCESS) { + messageApi.success( + t('execWorkflow.detail.overview.table.retryExecuteSuccess') + ); + handleClose(); + EventEmitter.emit(EmitterKey.Sql_Retry_Execute_Done, taskId); + } + }); + }, + { + manual: true + } + ); + + const handleClose = () => { + setSelectedRowKeys([]); + setAllSelectedKeys([]); + tableChange( + { + current: 1, + pageSize: 20 + }, + {}, + {}, + { + action: 'paginate', + currentDataSource: [] + } + ); + dispatch( + updateSqlExecWorkflowModalStatus({ + modalName: ModalName.Sql_Exec_Workflow_Retry_Execute_Modal, + status: false + }) + ); + }; + + useEffect(() => { + if (pageIndex && pageSize && visible) { + tableChange( + { + current: pageIndex, + pageSize: pageSize + }, + {}, + {}, + { + action: 'paginate', + currentDataSource: [] + } + ); + } + + if (execSqlId && !allSelectedKeys.length && visible) { + setAllSelectedKeys([execSqlId]); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [pageIndex, pageSize, visible, tableChange, execSqlId]); + + const handleRetryExecute = () => { + if (!allSelectedKeys.length) { + messageApi.error(t('execWorkflow.detail.overview.table.pleaseSelectSql')); + return; + } + retryExecute(); + }; + + return ( + + {t('execWorkflow.detail.overview.table.selectRetryExecuteSql')} + + {t('execWorkflow.detail.overview.table.selectRetryExecuteSqlDesc')} + + + } + footer={ + + {t('common.cancel')} + + {t('common.submit')} + + + } + closable={false} + centered + > + {contextHolder} + { + if (!sql) return null; + return ( + + ); + } + }, + { + dataIndex: 'exec_status', + width: 110, + title: () => t('execWorkflow.audit.table.execStatus'), + render: (status) => { + return ; + } + }, + { + dataIndex: 'exec_result', + className: 'ellipsis-column-width', + title: () => t('execWorkflow.audit.table.execResult'), + render: (result) => { + return result ? ( + + ) : ( + '-' + ); + } + } + ]} + dataSource={data?.data} + onChange={tableChange} + pagination={{ + total: data?.total_nums ?? 0, + current: pagination.page_index, + pageSize: pagination.page_size + }} + loading={loading || retryExecuteLoading} + rowSelection={{ + selectedRowKeys, + onChange: (selectedKeys) => { + setSelectedRowKeys(selectedKeys); + + const currentPageKeys = + data?.data?.map((item) => item.exec_sql_id) || []; + + const otherPageSelectedKeys = allSelectedKeys.filter( + (key) => !currentPageKeys.includes(Number(key)) + ); + const newAllSelectedKeys = [ + ...otherPageSelectedKeys, + ...selectedKeys + ]; + setAllSelectedKeys(newAllSelectedKeys); + }, + columnWidth: 60, + getCheckboxProps: (record) => { + return { + disabled: + record.exec_status === + getAuditTaskSQLsV2FilterExecStatusEnum.succeeded + }; + } + }} + scroll={{ y: '500px' }} + /> + + ); +}; + +export default RetryExecuteModal; diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/RetryExecuteModal/style.ts b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/RetryExecuteModal/style.ts new file mode 100644 index 000000000..e44ccf102 --- /dev/null +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/RetryExecuteModal/style.ts @@ -0,0 +1,17 @@ +import { styled } from '@mui/material'; +import { BasicModal } from '@actiontech/dms-kit'; +import { Typography } from 'antd'; + +export const RetryExecuteModalStyleWrapper = styled(BasicModal)` + & .ant-modal-body { + padding: 0 !important; + + .ant-table-wrapper.actiontech-table-namespace { + padding-bottom: 0; + } + } +`; + +export const RetryExecuteModalTitleDescStyleWrapper = styled(Typography.Text)` + font-weight: 100; +`; diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/FileMode.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/FileMode.tsx index c21ebadbc..5ee00f416 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/FileMode.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/FileMode.tsx @@ -13,14 +13,17 @@ import SqlStatementResultTable from '../SqlStatementResultTable'; import { Trans } from 'react-i18next'; import { TasksResultCardStyleWrapper } from './style'; import { SqlFileOutlined } from '@actiontech/icons'; -import { TypedLink } from '@actiontech/shared'; +import { TypedLink, useTypedParams } from '@actiontech/shared'; import { ROUTE_PATHS } from '@actiontech/dms-kit'; const FileMode: React.FC = ({ taskId, projectID, + enableRetryExecute, ...props }) => { + const { workflowId } = + useTypedParams(); const auditResult = useMemo(() => { const res: IAuditResult[] = []; @@ -90,7 +93,12 @@ const FileMode: React.FC = ({ { e.stopPropagation(); }} @@ -123,7 +131,12 @@ const FileMode: React.FC = ({ = ({
} + enableSqlRetryExecute={!!enableRetryExecute} instanceName={props.instanceName} schema={props.schema} /> @@ -150,7 +164,9 @@ const FileMode: React.FC = ({ requestErrorMessage, taskId, props.instanceName, - props.schema + props.schema, + enableRetryExecute, + workflowId ]); return ( diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/SqlMode.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/SqlMode.tsx index 597d2168c..c1fb8e50a 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/SqlMode.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/SqlMode.tsx @@ -34,15 +34,29 @@ import RollbackWorkflowEntry from './components/RollbackWorkflowEntry'; import { formatterSQL } from '@actiontech/dms-kit'; import AuditExceptionTree from './components/AuditExceptionTree'; import { IAuditResultItem } from '../../../../../../../../components/ReportDrawer/index.type'; +import { useDispatch } from 'react-redux'; +import { + updateRetryExecuteData, + updateSqlExecWorkflowModalStatus +} from '../../../../../../../../store/sqlExecWorkflow/index'; +import { ModalName } from '../../../../../../../../data/ModalName'; +import { + PermissionControl, + PERMISSIONS +} from '@actiontech/shared/lib/features'; + const SqlMode: React.FC = ({ projectID, taskId, onUpdateDescription, + pagination, + enableRetryExecute, ...props }) => { const { t } = useTranslation(); const [messageApi, contextHolder] = message.useMessage(); const { sqleTheme } = useThemeStyleData(); + const dispatch = useDispatch(); const [loading, { setTrue: updateDescPending, setFalse: updateDescDone }] = useBoolean(); const [currentContentKey, setCurrentContentKey] = @@ -82,6 +96,24 @@ const SqlMode: React.FC = ({ }) ); }; + + const onRetryExecute = () => { + dispatch( + updateSqlExecWorkflowModalStatus({ + modalName: ModalName.Sql_Exec_Workflow_Retry_Execute_Modal, + status: true + }) + ); + dispatch( + updateRetryExecuteData({ + taskId: taskId ?? '', + execSqlId: props.exec_sql_id ?? 0, + pageIndex: pagination?.page_index, + pageSize: pagination?.page_size + }) + ); + }; + const updateSqlDescribe = (sqlDescribe: string) => { updateDescPending(); task @@ -152,6 +184,24 @@ const SqlMode: React.FC = ({ /> {/* #endif */} + + + + {t('execWorkflow.detail.overview.table.retryExecute')} + + + {t('execWorkflow.audit.copyExecSql')} diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/__tests__/FileMode.test.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/__tests__/FileMode.test.tsx index d01885844..b0f9ee36d 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/__tests__/FileMode.test.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/__tests__/FileMode.test.tsx @@ -7,6 +7,12 @@ import task from '@actiontech/shared/lib/testUtil/mockApi/sqle/task'; import { sqleSuperRender } from '../../../../../../../../../testUtils/superRender'; import { TaskFileListMockData } from '@actiontech/shared/lib/testUtil/mockApi/sqle/task/data'; import { mockUseCurrentUser } from '@actiontech/shared/lib/testUtil/mockHook/mockUseCurrentUser'; +import { mockUsePermission } from '@actiontech/shared/lib/testUtil'; + +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useSelector: jest.fn() +})); describe('test TaskResultList/Result/FileMode', () => { let getTaskSQLsSpy: jest.SpyInstance; @@ -15,6 +21,9 @@ describe('test TaskResultList/Result/FileMode', () => { mockUseCurrentUser(); jest.useFakeTimers(); getTaskSQLsSpy = task.getAuditTaskSQLs(); + mockUsePermission(undefined, { + mockSelector: true + }); }); afterEach(() => { jest.useRealTimers(); @@ -26,6 +35,7 @@ describe('test TaskResultList/Result/FileMode', () => { taskId="123" projectID="300200" {...(TaskFileListMockData[0] as unknown as IAuditFileStatistic)} + enableRetryExecute /> ); diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/__tests__/SqlMode.test.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/__tests__/SqlMode.test.tsx index 0d4757669..b2003692f 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/__tests__/SqlMode.test.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/__tests__/SqlMode.test.tsx @@ -14,13 +14,23 @@ import { mockUseCurrentUser } from '@actiontech/shared/lib/testUtil/mockHook/moc import { AuditTaskSQLResV2BackupStrategyEnum } from '@actiontech/shared/lib/api/sqle/service/common.enum'; import { AuditTaskResV1StatusEnum } from '@actiontech/shared/lib/api/sqle/service/common.enum'; import { AuditTaskSQLsMockDataWithExceptionRule } from '@actiontech/shared/lib/testUtil/mockApi/sqle/task/data'; +import { useDispatch } from 'react-redux'; +import { mockUsePermission } from '@actiontech/shared/lib/testUtil'; +import { ModalName } from '../../../../../../../../../data/ModalName'; const projectID = '700300'; const taskId = 'task_id_1234'; +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useDispatch: jest.fn(), + useSelector: jest.fn() +})); + describe('sqle/ExecWorkflow/AuditDetail/SqlMode', () => { let requestUpdateSqlDesc: jest.SpyInstance; const onUpdateDescriptionFn = jest.fn(); + const dispatchSpy = jest.fn(); const customRender = ( params: Omit< @@ -46,6 +56,10 @@ describe('sqle/ExecWorkflow/AuditDetail/SqlMode', () => { jest.useFakeTimers(); requestUpdateSqlDesc = task.updateAuditTaskSQLs(); rule_template.getRuleList(); + (useDispatch as jest.Mock).mockImplementation(() => dispatchSpy); + mockUsePermission(undefined, { + mockSelector: true + }); }); afterEach(() => { @@ -311,4 +325,64 @@ describe('sqle/ExecWorkflow/AuditDetail/SqlMode', () => { expect(screen.getByText('审核异常')).toBeInTheDocument(); }); + + it('should render retry execute action when exec status is failed or initialized', async () => { + const { container } = customRender({ + number: 1, + exec_status: getAuditTaskSQLsV2FilterExecStatusEnum.failed, + enableRetryExecute: true, + exec_sql: 'select 1;' + }); + expect(container).toMatchSnapshot(); + expect(screen.getByText('再次执行')).toBeInTheDocument(); + + cleanup(); + + customRender({ + number: 1, + exec_status: getAuditTaskSQLsV2FilterExecStatusEnum.initialized, + enableRetryExecute: true, + exec_sql: 'select 1;' + }); + expect(container).toMatchSnapshot(); + expect(screen.getByText('再次执行')).toBeInTheDocument(); + }); + + it('should dispatch action when click retry execute button', async () => { + const mockData = { + taskId: 'testId', + pagination: { + page_index: 2, + page_size: 20 + }, + exec_sql_id: 0 + }; + customRender({ + number: 1, + exec_status: getAuditTaskSQLsV2FilterExecStatusEnum.failed, + enableRetryExecute: true, + exec_sql: 'select 1;', + ...mockData + }); + expect(screen.getByText('再次执行')).toBeInTheDocument(); + fireEvent.click(screen.getByText('再次执行')); + await act(async () => jest.advanceTimersByTime(0)); + expect(dispatchSpy).toHaveBeenCalledTimes(2); + expect(dispatchSpy).toHaveBeenNthCalledWith(1, { + type: 'sqlExecWorkflow/updateModalStatus', + payload: { + modalName: ModalName.Sql_Exec_Workflow_Retry_Execute_Modal, + status: true + } + }); + expect(dispatchSpy).toHaveBeenNthCalledWith(2, { + type: 'sqlExecWorkflow/updateRetryExecuteData', + payload: { + taskId: mockData.taskId, + execSqlId: mockData.exec_sql_id, + pageIndex: mockData.pagination.page_index, + pageSize: mockData.pagination.page_size + } + }); + }); }); diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/__tests__/__snapshots__/FileMode.test.tsx.snap b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/__tests__/__snapshots__/FileMode.test.tsx.snap index 20a0194e4..f01f09c6b 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/__tests__/__snapshots__/FileMode.test.tsx.snap +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/__tests__/__snapshots__/FileMode.test.tsx.snap @@ -84,7 +84,7 @@ exports[`test TaskResultList/Result/FileMode should match snapshot 1`] = ` create_table_if_not_exist.sql @@ -247,7 +247,7 @@ exports[`test TaskResultList/Result/FileMode should match snapshot 2`] = ` create_table_if_not_exist.sql @@ -410,7 +410,7 @@ exports[`test TaskResultList/Result/FileMode should render audit result count 1` create_table_if_not_exist.sql @@ -597,7 +597,7 @@ exports[`test TaskResultList/Result/FileMode should render collapse children and create_table_if_not_exist.sql @@ -689,7 +689,7 @@ exports[`test TaskResultList/Result/FileMode should render collapse children and class="ant-spin-container" >
当前仅展示前5条数据, 查看更多 @@ -719,6 +719,12 @@ exports[`test TaskResultList/Result/FileMode should render collapse children and + + + + 执行结果 + + 操作 + + +
+   +
+ + +
+ +
+ diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/__tests__/__snapshots__/SqlMode.ce.test.tsx.snap b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/__tests__/__snapshots__/SqlMode.ce.test.tsx.snap index ae28ee5fb..0f6560909 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/__tests__/__snapshots__/SqlMode.ce.test.tsx.snap +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/__tests__/__snapshots__/SqlMode.ce.test.tsx.snap @@ -60,6 +60,10 @@ exports[`sqle/ExecWorkflow/AuditDetail/SqlMode ce render init snap 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
`; + +exports[`sqle/ExecWorkflow/AuditDetail/SqlMode should render retry execute action when exec status is failed or initialized 1`] = ` +
+
+
+
+
+ + # + 1 + +
+
+
+ + 执行失败 + + +
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+ + + + + + + 所在文件 + + - + + +
+
+
+
+
+
+ + + + + + + 所在行 + + - + + +
+
+
+
+
+
+                
+                  
+ + 1 + + + select + + + + 1 + + ; +
+
+
+
+
+
+
+
+
+ +
+ +
+ +
+
+`; + +exports[`sqle/ExecWorkflow/AuditDetail/SqlMode should render retry execute action when exec status is failed or initialized 2`] = `
`; diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/index.type.ts b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/index.type.ts index 66bb23ae0..0d775f757 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/index.type.ts +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/ResultCard/index.type.ts @@ -7,9 +7,11 @@ import { WorkflowResV2ExecModeEnum, AuditTaskResV1StatusEnum } from '@actiontech/shared/lib/api/sqle/service/common.enum'; +import { TablePagination } from '@actiontech/dms-kit/es/components/ActiontechTable'; type BaseProps = { taskId: string; + enableRetryExecute?: boolean; }; export type SqlExecuteResultCardProps = BaseProps & @@ -22,6 +24,7 @@ export type SqlExecuteResultCardProps = BaseProps & taskStatus?: AuditTaskResV1StatusEnum; instanceName?: string; schema?: string; + pagination?: TablePagination; }; export type FileExecuteResultCardProps = BaseProps & diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/SqlStatementResultTable/__tests__/__snapshots__/index.test.tsx.snap b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/SqlStatementResultTable/__tests__/__snapshots__/index.test.tsx.snap index d5d2adf95..83087c2cc 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/SqlStatementResultTable/__tests__/__snapshots__/index.test.tsx.snap +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/SqlStatementResultTable/__tests__/__snapshots__/index.test.tsx.snap @@ -1,5 +1,326 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`test TaskResultList/SQLStatementResultTable render retry execute action 1`] = ` + +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ 序号 + + 执行语句 + + 审核结果 + + 执行结果 + + 操作 +
+ 1 + +
+
+ + + CREATE TABLE + + task ( id + + INT + + AUTO_INCREMENT + + PRIMARY KEY + + , title + + VARCHAR + + ( + + 255 + + ) + + NOT NULL + + , description TEXT, status ENUM( + + 'pending' + + , + + 'completed' + + ) + + NOT NULL + + + + DEFAULT + + + + 'pending' + + , created_at + + TIMESTAMP + + + + DEFAULT + + + +
+
+ + + +
+
+
+
+
+
+ + + +
+
+
+
+ + 准备执行 + + +
+ +
+
+
+
+
+
+
+
+
+
+ +`; + exports[`test TaskResultList/SQLStatementResultTable should match snapshot 1`] = `
@@ -16,7 +337,7 @@ exports[`test TaskResultList/SQLStatementResultTable should match snapshot 1`] = class="ant-spin-container" >
+ + + + 执行结果 + + 操作 + + +
+   +
+ + +
+ +
+ @@ -298,7 +658,7 @@ exports[`test TaskResultList/SQLStatementResultTable should match snapshot 2`] = class="ant-spin-container" >
+ + + + 执行结果 + + 操作 + + +
+   +
+ + +
+ +
+ @@ -945,7 +1344,7 @@ exports[`test TaskResultList/SQLStatementResultTable should render exec result t class="ant-spin-container" >
+ + + + 执行结果 + + 操作 + + +
+   +
+
+ +
+ +
+ diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/SqlStatementResultTable/__tests__/index.test.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/SqlStatementResultTable/__tests__/index.test.tsx index 3c7f4a7f7..4a0699aa6 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/SqlStatementResultTable/__tests__/index.test.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/SqlStatementResultTable/__tests__/index.test.tsx @@ -12,13 +12,25 @@ import { ignoreConsoleErrors, UtilsConsoleErrorStringsEnum } from '@actiontech/shared/lib/testUtil/common'; +import { useDispatch } from 'react-redux'; +import { mockUsePermission } from '@actiontech/shared/lib/testUtil'; +import { ModalName } from '../../../../../../../../../data/ModalName'; +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useDispatch: jest.fn(), + useSelector: jest.fn() +})); describe('test TaskResultList/SQLStatementResultTable', () => { ignoreConsoleErrors([UtilsConsoleErrorStringsEnum.UNKNOWN_EVENT_HANDLER]); - + const dispatchSpy = jest.fn(); beforeEach(() => { jest.useFakeTimers(); mockUseCurrentUser(); + (useDispatch as jest.Mock).mockImplementation(() => dispatchSpy); + mockUsePermission(undefined, { + mockSelector: true + }); }); afterEach(() => { cleanup(); @@ -66,6 +78,7 @@ describe('test TaskResultList/SQLStatementResultTable', () => { exec_status: 'initialized' } ]} + enableSqlRetryExecute /> ); @@ -110,9 +123,77 @@ describe('test TaskResultList/SQLStatementResultTable', () => { "exec sql failed: \nSELECT * FROM ab.xx; \nError 1049 (42000): Unknown database 'ab'" } ]} + enableSqlRetryExecute={false} /> ); expect(baseElement).toMatchSnapshot(); }); + + it('render retry execute action', async () => { + const openSpy = jest.spyOn(window, 'open'); + openSpy.mockImplementation(jest.fn()); + rule_template.getRuleList(); + const { baseElement } = sqleSuperRender( + + ); + + expect(baseElement).toMatchSnapshot(); + + expect(screen.getByText('再次执行')).toBeInTheDocument(); + fireEvent.click(screen.getByText('再次执行')); + await act(async () => jest.advanceTimersByTime(0)); + expect(dispatchSpy).toHaveBeenCalledTimes(2); + + expect(dispatchSpy).toHaveBeenNthCalledWith(1, { + type: 'sqlExecWorkflow/updateRetryExecuteData', + payload: { + taskId: '1' + } + }); + expect(dispatchSpy).toHaveBeenNthCalledWith(2, { + type: 'sqlExecWorkflow/updateModalStatus', + payload: { + modalName: ModalName.Sql_Exec_Workflow_Retry_Execute_Modal, + status: true + } + }); + }); }); diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/SqlStatementResultTable/actions.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/SqlStatementResultTable/actions.tsx new file mode 100644 index 000000000..0815009f5 --- /dev/null +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/SqlStatementResultTable/actions.tsx @@ -0,0 +1,39 @@ +import { + ActiontechTableActionsWithPermissions, + PERMISSIONS +} from '@actiontech/shared/lib/features'; +import { IAuditTaskSQLResV2 } from '@actiontech/shared/lib/api/sqle/service/common'; +import { t } from '../../../../../../../../locale'; +import { getAuditTaskSQLsV2FilterExecStatusEnum } from '@actiontech/shared/lib/api/sqle/service/task/index.enum'; + +type Params = { + enableSqlRetryExecute: boolean; + onRetryExecute: (record: IAuditTaskSQLResV2) => void; +}; + +export const SQLStatementResultActions = ( + params: Params +): ActiontechTableActionsWithPermissions => { + return { + width: 100, + buttons: [ + { + key: 'retryExecute', + text: t('execWorkflow.detail.overview.table.retryExecute'), + buttonProps(record) { + return { + disabled: !params.enableSqlRetryExecute, + hidden: ![ + getAuditTaskSQLsV2FilterExecStatusEnum.failed, + getAuditTaskSQLsV2FilterExecStatusEnum.initialized + ].includes( + record?.exec_status as getAuditTaskSQLsV2FilterExecStatusEnum + ), + onClick: () => params.onRetryExecute(record!) + }; + }, + permissions: PERMISSIONS.ACTIONS.SQLE.SQL_EXEC_WORKFLOW.EXEC_TASK + } + ] + }; +}; diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/SqlStatementResultTable/columns.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/SqlStatementResultTable/columns.tsx index f32802bde..2fb4388f4 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/SqlStatementResultTable/columns.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/SqlStatementResultTable/columns.tsx @@ -2,14 +2,14 @@ import { IAuditTaskSQLResV2 } from '@actiontech/shared/lib/api/sqle/service/comm import ExecStatusTag from '../ResultCard/components/ExecStatusTag'; import { getAuditTaskSQLsV2FilterExecStatusEnum } from '@actiontech/shared/lib/api/sqle/service/task/index.enum'; import { SQLRenderer } from '@actiontech/shared'; -import { BasicTableProps } from '@actiontech/dms-kit/es/components/BasicTable/BasicTable.types'; import { BasicToolTip } from '@actiontech/dms-kit'; import { t } from '../../../../../../../../locale'; import ResultIconRender from '../../../../../../../../components/AuditResultMessage/ResultIconRender'; +import { ActiontechTableColumn } from '@actiontech/dms-kit/es/components/ActiontechTable'; export const SQLStatementResultColumns = ( onClickAuditResult: (record: IAuditTaskSQLResV2) => void -): BasicTableProps['columns'] => { +): ActiontechTableColumn => { return [ { dataIndex: 'number', @@ -56,10 +56,12 @@ export const SQLStatementResultColumns = ( dataIndex: 'exec_status', title: () => t('audit.table.execResult'), className: 'exec-status-column', - render: (status: getAuditTaskSQLsV2FilterExecStatusEnum, record) => { + render: (status, record) => { return ( - + ); } diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/SqlStatementResultTable/index.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/SqlStatementResultTable/index.tsx index 3373812d9..9ed0de7c0 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/SqlStatementResultTable/index.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/Common/SqlStatementResultTable/index.tsx @@ -1,4 +1,4 @@ -import { BasicTable } from '@actiontech/dms-kit'; +import { ActiontechTable } from '@actiontech/dms-kit'; import { SqlStatementResultTableProps } from './index.type'; import { SQLStatementResultColumns } from './columns'; import { useBoolean } from 'ahooks'; @@ -9,6 +9,13 @@ import AuditResultDrawer from '../../../../../../Common/AuditResultList/Table/Au import { useCurrentProject } from '@actiontech/shared/lib/features'; import { parse2ReactRouterPath } from '@actiontech/shared/lib/components/TypedRouter/utils'; import { ROUTE_PATHS } from '@actiontech/dms-kit'; +import { SQLStatementResultActions } from './actions'; +import { usePermission } from '@actiontech/shared/lib/features'; +import { useDispatch } from 'react-redux'; +import { updateRetryExecuteData } from '../../../../../../../../store/sqlExecWorkflow'; +import { updateSqlExecWorkflowModalStatus } from '../../../../../../../../store/sqlExecWorkflow'; +import { ModalName } from '../../../../../../../../data/ModalName'; + const SqlStatementResultTable: React.FC = ( props ) => { @@ -16,9 +23,11 @@ const SqlStatementResultTable: React.FC = ( auditResultDrawerVisibility, { setFalse: closeAuditResultDrawer, setTrue: openAuditResultDrawer } ] = useBoolean(); + const dispatch = useDispatch(); const { projectID } = useCurrentProject(); const [currentAuditResultRecord, setCurrentAuditResultRecord] = useState(); + const { parse2TableActionPermissions } = usePermission(); const onClickAuditResult = (record: IAuditTaskSQLResV2) => { openAuditResultDrawer(); setCurrentAuditResultRecord(record); @@ -42,12 +51,33 @@ const SqlStatementResultTable: React.FC = ( }) ); }; + + const onRetryExecute = () => { + dispatch( + updateRetryExecuteData({ + taskId: props.taskId ?? '' + }) + ); + dispatch( + updateSqlExecWorkflowModalStatus({ + modalName: ModalName.Sql_Exec_Workflow_Retry_Execute_Modal, + status: true + }) + ); + }; + return ( - & { taskId?: string; instanceName?: string; schema?: string }; +> & { + taskId?: string; + instanceName?: string; + schema?: string; + enableSqlRetryExecute: boolean; +}; diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/FileExecuteMode/__tests__/__snapshots__/index.test.tsx.snap b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/FileExecuteMode/__tests__/__snapshots__/index.test.tsx.snap index 23ccaf753..2778c73f3 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/FileExecuteMode/__tests__/__snapshots__/index.test.tsx.snap +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/FileExecuteMode/__tests__/__snapshots__/index.test.tsx.snap @@ -385,7 +385,7 @@ exports[`test PaginationList/FileExecuteMode should match snapshot 2`] = ` create_table_if_not_exist.sql @@ -547,7 +547,7 @@ exports[`test PaginationList/FileExecuteMode should match snapshot 2`] = ` create_table_roll_back.sql @@ -685,7 +685,7 @@ exports[`test PaginationList/FileExecuteMode should match snapshot 2`] = ` new_folder/create_table_if_not_exist.sql @@ -847,7 +847,7 @@ exports[`test PaginationList/FileExecuteMode should match snapshot 2`] = ` new_folder/create_table_roll_back.sql @@ -985,7 +985,7 @@ exports[`test PaginationList/FileExecuteMode should match snapshot 2`] = ` new_folder/in_folder/create_table_if_not_exist.sql @@ -1147,7 +1147,7 @@ exports[`test PaginationList/FileExecuteMode should match snapshot 2`] = ` new_folder/in_folder/create_table_roll_back.sql diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/FileExecuteMode/__tests__/index.test.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/FileExecuteMode/__tests__/index.test.tsx index 9de4ebec8..3467036bb 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/FileExecuteMode/__tests__/index.test.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/FileExecuteMode/__tests__/index.test.tsx @@ -22,6 +22,7 @@ describe('test PaginationList/FileExecuteMode', () => { tableChange: jest.fn(), assigneeUserNames: [mockCurrentUserReturn.username], workflowStatus: WorkflowRecordResV2StatusEnum.wait_for_execution, + enableRetryExecute: true, ...params }; return sqleSuperRender(); diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/FileExecuteMode/index.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/FileExecuteMode/index.tsx index 981bc70ef..55ad5bdd6 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/FileExecuteMode/index.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/FileExecuteMode/index.tsx @@ -25,7 +25,8 @@ const FileExecuteMode: React.FC = ({ auditResultActiveKey, assigneeUserNames, instanceName, - schema + schema, + enableRetryExecute }) => { const { t } = useTranslation(); @@ -93,6 +94,7 @@ const FileExecuteMode: React.FC = ({ executeMode={WorkflowResV2ExecModeEnum.sql_file} instanceName={instanceName} schema={schema} + enableRetryExecute={enableRetryExecute} /> ); diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/SqlExecuteMode/__tests__/__snapshots__/index.test.tsx.snap b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/SqlExecuteMode/__tests__/__snapshots__/index.test.tsx.snap index 68432d6b6..21f99ec1a 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/SqlExecuteMode/__tests__/__snapshots__/index.test.tsx.snap +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/SqlExecuteMode/__tests__/__snapshots__/index.test.tsx.snap @@ -306,6 +306,19 @@ exports[`test PaginationList/SQLExecuteMode should match snapshot 2`] = `
+
+ +
({ + ...jest.requireActual('react-redux'), + useSelector: jest.fn() +})); describe('test PaginationList/SQLExecuteMode', () => { const customRender = (params?: Partial) => { @@ -20,7 +26,8 @@ describe('test PaginationList/SQLExecuteMode', () => { pagination: { page_index: 1, page_size: 20 }, tableChange: jest.fn(), assigneeUserNames: [mockCurrentUserReturn.username], - execStatusFilterValue: null + execStatusFilterValue: null, + enableRetryExecute: true }; return sqleSuperRender(); }; @@ -29,6 +36,9 @@ describe('test PaginationList/SQLExecuteMode', () => { mockUseCurrentUser(); jest.useFakeTimers(); mockUseCurrentProject(); + mockUsePermission(undefined, { + mockSelector: true + }); }); afterEach(() => { jest.useRealTimers(); diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/SqlExecuteMode/index.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/SqlExecuteMode/index.tsx index 529601fc6..a8e7b48ae 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/SqlExecuteMode/index.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/SqlExecuteMode/index.tsx @@ -24,7 +24,8 @@ const SqlExecuteMode: React.FC = ({ enableBackup, taskStatus, instanceName, - schema + schema, + enableRetryExecute }) => { const { t } = useTranslation(); @@ -91,6 +92,8 @@ const SqlExecuteMode: React.FC = ({ taskStatus={taskStatus} instanceName={instanceName} schema={schema} + pagination={pagination} + enableRetryExecute={enableRetryExecute} /> ); diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/__tests__/__snapshots__/index.test.tsx.snap b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/__tests__/__snapshots__/index.test.tsx.snap index 04ef856cb..7abd24834 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/__tests__/__snapshots__/index.test.tsx.snap +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/__tests__/__snapshots__/index.test.tsx.snap @@ -15,6 +15,7 @@ exports[`test ExecWorkflow/TaskResultList/PaginationList should match snapshot 1 } auditResultActiveKey="123" currentListLayout="pagination" + enableRetryExecute={true} execStatusFilterValue={null} noDuplicate={false} pagination={ @@ -37,6 +38,7 @@ exports[`test ExecWorkflow/TaskResultList/PaginationList should match snapshot 1 } auditResultActiveKey="123" currentListLayout="pagination" + enableRetryExecute={true} execStatusFilterValue={null} noDuplicate={false} pagination={ @@ -69,6 +71,7 @@ exports[`test ExecWorkflow/TaskResultList/PaginationList should match snapshot 2 } auditResultActiveKey="123" currentListLayout="pagination" + enableRetryExecute={true} execStatusFilterValue={null} noDuplicate={false} pagination={ @@ -91,6 +94,7 @@ exports[`test ExecWorkflow/TaskResultList/PaginationList should match snapshot 2 } auditResultActiveKey="123" currentListLayout="pagination" + enableRetryExecute={true} execStatusFilterValue={null} noDuplicate={false} pagination={ diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/__tests__/index.test.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/__tests__/index.test.tsx index 212543243..62afaea8f 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/__tests__/index.test.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/PaginationList/__tests__/index.test.tsx @@ -17,7 +17,8 @@ describe('test ExecWorkflow/TaskResultList/PaginationList', () => { tableFilterInfo: {}, pagination: { page_index: 1, page_size: 10 }, assigneeUserNames: [mockCurrentUserReturn.username], - execStatusFilterValue: null + execStatusFilterValue: null, + enableRetryExecute: true }; expect( diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/SqlFileStatementOverview/__tests__/__snapshots__/index.test.tsx.snap b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/SqlFileStatementOverview/__tests__/__snapshots__/index.test.tsx.snap index b7a329d3e..7e3d773dd 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/SqlFileStatementOverview/__tests__/__snapshots__/index.test.tsx.snap +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/SqlFileStatementOverview/__tests__/__snapshots__/index.test.tsx.snap @@ -271,7 +271,7 @@ exports[`test AuditDetail/SqlFileStatementOverview should match snapshot 1`] = ` class="ant-spin-container ant-spin-blur" >
+ + + + 执行结果 + + 操作 + + +
+   +
+
+ + + + 执行结果 + + 操作 + + +
+   +
+ + +
+ +
+ diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/SqlFileStatementOverview/__tests__/index.test.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/SqlFileStatementOverview/__tests__/index.test.tsx index 4a1f0d700..cd26c031f 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/SqlFileStatementOverview/__tests__/index.test.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/SqlFileStatementOverview/__tests__/index.test.tsx @@ -8,6 +8,12 @@ import { ignoreConsoleErrors } from '@actiontech/shared/lib/testUtil/common'; import { mockUseCurrentUser } from '@actiontech/shared/lib/testUtil/mockHook/mockUseCurrentUser'; +import execWorkflow from '@actiontech/shared/lib/testUtil/mockApi/sqle/execWorkflow'; +import { useDispatch, useSelector } from 'react-redux'; +import { ModalName } from '../../../../../../../../data/ModalName'; +import EmitterKey from '../../../../../../../../data/EmitterKey'; +import EventEmitter from '../../../../../../../../utils/EventEmitter'; +import * as useRetryExecuteHook from '../../../hooks/useRetryExecute'; jest.mock('react-router-dom', () => { return { @@ -17,11 +23,21 @@ jest.mock('react-router-dom', () => { }; }); +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useDispatch: jest.fn(), + useSelector: jest.fn() +})); + describe('test AuditDetail/SqlFileStatementOverview', () => { let getTaskSQLsSpy: jest.SpyInstance; let getAuditFileExecStatistic: jest.SpyInstance; const useParamsMock: jest.Mock = useParams as jest.Mock; const navigateSpy = jest.fn(); + const dispatchSpy = jest.fn(); + + let getWorkflowSpy: jest.SpyInstance; + let getSummaryOfInstanceTasksSpy: jest.SpyInstance; ignoreConsoleErrors([UtilsConsoleErrorStringsEnum.UNIQUE_KEY_REQUIRED]); @@ -30,8 +46,34 @@ describe('test AuditDetail/SqlFileStatementOverview', () => { jest.useFakeTimers(); getTaskSQLsSpy = task.getAuditTaskSQLs(); getAuditFileExecStatistic = task.getAuditFileExecStatistic(); - useParamsMock.mockReturnValue({ taskId: '15', fileId: '434' }); + useParamsMock.mockReturnValue({ + taskId: '15', + fileId: '434', + workflowId: '123' + }); (useNavigate as jest.Mock).mockImplementation(() => navigateSpy); + getWorkflowSpy = execWorkflow.getWorkflow(); + getSummaryOfInstanceTasksSpy = execWorkflow.getSummaryOfInstanceTasks(); + (useDispatch as jest.Mock).mockImplementation(() => dispatchSpy); + (useSelector as jest.Mock).mockImplementation((selector) => + selector({ + permission: { + moduleFeatureSupport: { + sqlOptimization: false, + knowledge: false + }, + userOperationPermissions: null + }, + sqlExecWorkflow: { + retryExecuteData: {}, + modalStatus: { + [ModalName.Sql_Exec_Workflow_Retry_Execute_Modal]: false + } + } + }) + ); + const spy = jest.spyOn(useRetryExecuteHook, 'default'); + spy.mockImplementation(() => ({ enableRetryExecute: true })); }); afterEach(() => { jest.useRealTimers(); @@ -55,6 +97,10 @@ describe('test AuditDetail/SqlFileStatementOverview', () => { filter_exec_status: undefined, no_duplicate: false }); + + expect(getWorkflowSpy).toHaveBeenCalledTimes(1); + expect(getSummaryOfInstanceTasksSpy).toHaveBeenCalledTimes(1); + expect(container).toMatchSnapshot(); await act(async () => jest.advanceTimersByTime(3000)); @@ -99,4 +145,27 @@ describe('test AuditDetail/SqlFileStatementOverview', () => { no_duplicate: true }); }); + + it('should refresh file sql when emit Sql_Retry_Execute_Done event', async () => { + sqleSuperRender(); + await act(async () => jest.advanceTimersByTime(3000)); + expect(getTaskSQLsSpy).toHaveBeenCalledTimes(1); + act(() => { + EventEmitter.emit(EmitterKey.Sql_Retry_Execute_Done); + }); + expect(getTaskSQLsSpy).toHaveBeenCalledTimes(2); + }); + + it('should init modal status', async () => { + sqleSuperRender(); + expect(dispatchSpy).toHaveBeenCalledTimes(1); + expect(dispatchSpy).toHaveBeenCalledWith({ + type: 'sqlExecWorkflow/initModalStatus', + payload: { + modalStatus: { + [ModalName.Sql_Exec_Workflow_Retry_Execute_Modal]: false + } + } + }); + }); }); 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 b5d6c2369..287e3a463 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 @@ -38,13 +38,27 @@ import DownloadRecord from '../../../../../Common/DownloadRecord'; import { AuditResultFilterContainerStyleWrapper } from '../../../../../Common/AuditResultFilterContainer/style'; import { LeftArrowOutlined, SqlFileOutlined } from '@actiontech/icons'; import { ROUTE_PATHS } from '@actiontech/dms-kit'; +import { ModalName } from '../../../../../../../data/ModalName'; +import EmitterKey from '../../../../../../../data/EmitterKey'; +import EventEmitter from '../../../../../../../utils/EventEmitter'; +import { initSqlExecWorkflowModalStatus } from '../../../../../../../store/sqlExecWorkflow'; +import { useEffect } from 'react'; +import { useDispatch } from 'react-redux'; +import RetryExecuteModal from '../../RetryExecuteModal'; +import workflow from '@actiontech/shared/lib/api/sqle/service/workflow'; +import { useCurrentProject } from '@actiontech/shared/lib/features'; +import useRetryExecute from '../../hooks/useRetryExecute'; + const SqlFileStatementOverview: React.FC = () => { const { t } = useTranslation(); - const { taskId, fileId } = + const dispatch = useDispatch(); + const { taskId, fileId, workflowId } = useTypedParams< typeof ROUTE_PATHS.SQLE.SQL_EXEC_WORKFLOW.sql_files_overview >(); + const { projectName } = useCurrentProject(); + const extractQuery = useTypedQuery(); const navigate = useTypedNavigate(); @@ -65,6 +79,37 @@ const SqlFileStatementOverview: React.FC = () => { updateTableFilterInfo, AuditTaskExtraFilterMeta() ); + + const { data: workflowInfo, loading: getWorkflowLoading } = useRequest( + () => + workflow + .getWorkflowV2({ + project_name: projectName, + workflow_id: workflowId ?? '' + }) + .then((res) => res.data.data), + { + ready: !!workflowId + } + ); + + const { loading: taskListLoading, data: currentTask } = useRequest( + () => + workflow + .getSummaryOfInstanceTasksV2({ + workflow_id: workflowId ?? '', + project_name: projectName + }) + .then((res) => { + if (res.data.code === ResponseCode.SUCCESS) { + return res.data.data?.find((i) => i.task_id?.toString() === taskId); + } + }), + { + ready: !!workflowId + } + ); + const { data: currentFileOverview } = useRequest(() => task .getAuditFileExecStatistic({ @@ -77,7 +122,7 @@ const SqlFileStatementOverview: React.FC = () => { } }) ); - const { data, loading } = useRequest( + const { data, loading, refresh } = useRequest( () => handleTableRequestError( task.getAuditTaskSQLsV2({ @@ -104,6 +149,29 @@ const SqlFileStatementOverview: React.FC = () => { ROUTE_PATHS.SQLE.SQL_EXEC_WORKFLOW.sql_files_overview ); + useEffect(() => { + dispatch( + initSqlExecWorkflowModalStatus({ + modalStatus: { + [ModalName.Sql_Exec_Workflow_Retry_Execute_Modal]: false + } + }) + ); + }, [dispatch]); + + const { enableRetryExecute } = useRetryExecute({ + currentTask, + workflowInfo + }); + + useEffect(() => { + const { unsubscribe } = EventEmitter.subscribe( + EmitterKey.Sql_Retry_Execute_Done, + refresh + ); + return unsubscribe; + }, [refresh]); + return ( { { isPaginationFixed instanceName={searchParams?.instance_name ?? ''} schema={searchParams?.schema ?? ''} + enableSqlRetryExecute={enableRetryExecute} /> + ); }; diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/FileExecuteMode/__tests__/__snapshots__/index.test.tsx.snap b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/FileExecuteMode/__tests__/__snapshots__/index.test.tsx.snap index 860d0f006..64d0689b5 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/FileExecuteMode/__tests__/__snapshots__/index.test.tsx.snap +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/FileExecuteMode/__tests__/__snapshots__/index.test.tsx.snap @@ -247,7 +247,7 @@ exports[`test WaterfallList/FileExecuteMode should match snapshot 2`] = ` create_table_if_not_exist.sql @@ -409,7 +409,7 @@ exports[`test WaterfallList/FileExecuteMode should match snapshot 2`] = ` create_table_roll_back.sql @@ -547,7 +547,7 @@ exports[`test WaterfallList/FileExecuteMode should match snapshot 2`] = ` new_folder/create_table_if_not_exist.sql @@ -709,7 +709,7 @@ exports[`test WaterfallList/FileExecuteMode should match snapshot 2`] = ` new_folder/create_table_roll_back.sql @@ -847,7 +847,7 @@ exports[`test WaterfallList/FileExecuteMode should match snapshot 2`] = ` new_folder/in_folder/create_table_if_not_exist.sql @@ -1009,7 +1009,7 @@ exports[`test WaterfallList/FileExecuteMode should match snapshot 2`] = ` new_folder/in_folder/create_table_roll_back.sql diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/FileExecuteMode/__tests__/index.test.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/FileExecuteMode/__tests__/index.test.tsx index b29d4bc10..abfd9ca11 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/FileExecuteMode/__tests__/index.test.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/FileExecuteMode/__tests__/index.test.tsx @@ -20,6 +20,7 @@ describe('test WaterfallList/FileExecuteMode', () => { assigneeUserNames: [mockCurrentUserReturn.username], workflowStatus: WorkflowRecordResV2StatusEnum.wait_for_execution, execStatusFilterValue: null, + enableRetryExecute: true, ...params }; return sqleSuperRender(); diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/FileExecuteMode/index.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/FileExecuteMode/index.tsx index 97b50aa7b..e3c60e261 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/FileExecuteMode/index.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/FileExecuteMode/index.tsx @@ -25,7 +25,8 @@ const FileExecuteMode: React.FC = ({ auditResultActiveKey, assigneeUserNames, instanceName, - schema + schema, + enableRetryExecute }) => { const { projectID } = useCurrentProject(); const scrollPageNumber = useRef(0); @@ -119,6 +120,7 @@ const FileExecuteMode: React.FC = ({ taskId={taskId} instanceName={instanceName} schema={schema} + enableRetryExecute={enableRetryExecute} /> ); diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/SqlExecuteMode/__tests__/__snapshots__/index.test.tsx.snap b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/SqlExecuteMode/__tests__/__snapshots__/index.test.tsx.snap index 69f0ce17a..221ea16b0 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/SqlExecuteMode/__tests__/__snapshots__/index.test.tsx.snap +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/SqlExecuteMode/__tests__/__snapshots__/index.test.tsx.snap @@ -115,6 +115,19 @@ exports[`test WaterfallList/SQLExecuteMode render onUpdateDescription 1`] = `
+
+ +
+
+ +
({ + ...jest.requireActual('react-redux'), + useSelector: jest.fn() +})); + describe('test WaterfallList/SQLExecuteMode', () => { const customRender = (params?: Partial) => { const _params: SqlExecuteModeProps = { @@ -39,7 +45,8 @@ describe('test WaterfallList/SQLExecuteMode', () => { noDuplicate: false, tableFilterInfo: {}, assigneeUserNames: [mockCurrentUserReturn.username], - execStatusFilterValue: null + execStatusFilterValue: null, + enableRetryExecute: true }; return sqleSuperRender(); }; @@ -48,6 +55,9 @@ describe('test WaterfallList/SQLExecuteMode', () => { mockUseCurrentUser(); jest.useFakeTimers(); mockUseCurrentProject(); + mockUsePermission(undefined, { + mockSelector: true + }); }); afterEach(() => { jest.useRealTimers(); diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/SqlExecuteMode/index.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/SqlExecuteMode/index.tsx index e3fb9649c..d7989506c 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/SqlExecuteMode/index.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/SqlExecuteMode/index.tsx @@ -25,7 +25,8 @@ const SqlExecuteMode: React.FC = ({ enableBackup, taskStatus, instanceName, - schema + schema, + enableRetryExecute }) => { const { projectID } = useCurrentProject(); const scrollPageNumber = useRef(0); @@ -177,6 +178,7 @@ const SqlExecuteMode: React.FC = ({ taskStatus={taskStatus} instanceName={instanceName} schema={schema} + enableRetryExecute={enableRetryExecute} /> ); diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/__tests__/__snapshots__/index.test.tsx.snap b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/__tests__/__snapshots__/index.test.tsx.snap index f322b8344..cf1be5b76 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/__tests__/__snapshots__/index.test.tsx.snap +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/__tests__/__snapshots__/index.test.tsx.snap @@ -15,6 +15,7 @@ exports[`test ExecWorkflow/TaskResultList/WaterfallList should match snapshot 1` } auditResultActiveKey="123" currentListLayout="pagination" + enableRetryExecute={true} execStatusFilterValue={null} noDuplicate={false} tableFilterInfo={{}} @@ -30,6 +31,7 @@ exports[`test ExecWorkflow/TaskResultList/WaterfallList should match snapshot 1` } auditResultActiveKey="123" currentListLayout="pagination" + enableRetryExecute={true} execStatusFilterValue={null} noDuplicate={false} tableFilterInfo={{}} @@ -55,6 +57,7 @@ exports[`test ExecWorkflow/TaskResultList/WaterfallList should match snapshot 2` } auditResultActiveKey="123" currentListLayout="pagination" + enableRetryExecute={true} execStatusFilterValue={null} noDuplicate={false} tableFilterInfo={{}} @@ -70,6 +73,7 @@ exports[`test ExecWorkflow/TaskResultList/WaterfallList should match snapshot 2` } auditResultActiveKey="123" currentListLayout="pagination" + enableRetryExecute={true} execStatusFilterValue={null} noDuplicate={false} tableFilterInfo={{}} diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/__tests__/index.test.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/__tests__/index.test.tsx index 74c8cadbd..52bf459db 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/__tests__/index.test.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/WaterfallList/__tests__/index.test.tsx @@ -15,7 +15,8 @@ describe('test ExecWorkflow/TaskResultList/WaterfallList', () => { noDuplicate: false, tableFilterInfo: {}, assigneeUserNames: [mockCurrentUserReturn.username], - execStatusFilterValue: null + execStatusFilterValue: null, + enableRetryExecute: true }; expect( diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/__tests__/__snapshots__/index.test.tsx.snap b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/__tests__/__snapshots__/index.test.tsx.snap index 76e1a8db0..6b5535999 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/__tests__/__snapshots__/index.test.tsx.snap +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/__tests__/__snapshots__/index.test.tsx.snap @@ -9,6 +9,7 @@ exports[`test ExecWorkflow/TaskResultList should match snapshot when currentList } auditResultActiveKey="123" currentListLayout="pagination" + enableRetryExecute={true} execStatusFilterValue={null} executeMode="sqls" noDuplicate={false} @@ -33,6 +34,7 @@ exports[`test ExecWorkflow/TaskResultList should match snapshot when currentList } auditResultActiveKey="123" currentListLayout="scroll" + enableRetryExecute={true} execStatusFilterValue={null} executeMode="sqls" noDuplicate={false} diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/__tests__/index.test.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/__tests__/index.test.tsx index a761abe47..6cf32861e 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/__tests__/index.test.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/__tests__/index.test.tsx @@ -19,6 +19,7 @@ describe('test ExecWorkflow/TaskResultList', () => { pagination={{ page_index: 1, page_size: 10 }} assigneeUserNames={[mockCurrentUserReturn.username]} execStatusFilterValue={null} + enableRetryExecute /> ); @@ -38,6 +39,7 @@ describe('test ExecWorkflow/TaskResultList', () => { pagination={{ page_index: 1, page_size: 10 }} assigneeUserNames={[mockCurrentUserReturn.username]} execStatusFilterValue={null} + enableRetryExecute /> ); diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/index.type.ts b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/index.type.ts index f974fab78..d6eb60a36 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/index.type.ts +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/TaskResultList/index.type.ts @@ -23,4 +23,5 @@ export type TasksResultListBaseProps = { taskStatus?: AuditTaskResV1StatusEnum; instanceName?: string; schema?: string; + enableRetryExecute: boolean; }; diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/__tests__/__snapshots__/index.test.tsx.snap b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/__tests__/__snapshots__/index.test.tsx.snap index 00b3d1ec2..1ee412322 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/__tests__/__snapshots__/index.test.tsx.snap +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/__tests__/__snapshots__/index.test.tsx.snap @@ -1108,6 +1108,10 @@ exports[`test AuditExecResultPanel matches snapshot and selects task on initial
+
+
+ +
+
+ +
`; -exports[`test AuditExecResultPanel updates layout to waterfall and retrieves task SQLs 1`] = ` +exports[`test AuditExecResultPanel sql retry execute permission should enable retry execute when task is failed and current time enable execute workflow 1`] = `
- - 2000 - % - + - @@ -2757,11 +2783,7 @@ exports[`test AuditExecResultPanel updates layout to waterfall and retrieves tas
- - 30 - + - @@ -2771,120 +2793,1112 @@ exports[`test AuditExecResultPanel updates layout to waterfall and retrieves tas
-
- - instance_schema - - - Schema - -
-
+ />
-
-
-
    -
  • + + # + 1 + +
    +
    -
    - - # - 1 - -
    + 准备执行 + +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    -
    +
    +
    +
    +
    +
    +
    +
    + + + + + + + 所在文件 + + - + + +
    +
    +
    +
    +
    +
    + + + + + + + 所在行 + + - + + +
    +
    +
    +
    +
    +
    +                              
    +                                
    + + 1 + + + SELECT + + + + 1 + + ; +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    + +
    + +
    +
  • +
+
+
+
+
    +
  • + + 共 1 条数据 + +
  • +
  • + +
  • +
  • + + 1 + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+
+ +
+`; + +exports[`test AuditExecResultPanel updates layout to waterfall and retrieves task SQLs 1`] = ` +
+
+
+ 审核结果 +
+
+
+
+ + + +
+
+
+
+
+ +
+
+ +
+
+ 数据去重 +
+
+
+ +
+
+ +
+
+
+
+
+ 全部状态 +
+
+ 准备执行 +
+
+ 正在执行 +
+
+ 执行成功 +
+
+ 执行失败 +
+
+ 人工执行 +
+
+ 正在中止 +
+
+ 中止成功 +
+
+ 中止失败 +
+
+ 执行回滚 +
+
+
+
+
+ + 2000 + % + + + 审核通过率 + +
+
+
+
+ + 30 + + + 审核结果评分 + +
+
+
+
+ + instance_schema + + + Schema + +
+
+
+
+
+
+
+
+
+
+
    +
  • +
    +
    +
    +
    + + # + 1 + +
    +
    +
    + + 准备执行 + + +
    +
    +
    +
    +
    +
    ({ ...jest.requireActual('react-redux'), - useSelector: jest.fn() + useSelector: jest.fn(), + useDispatch: jest.fn() })); describe('test AuditExecResultPanel', () => { + const dispatchSpy = jest.fn(); const activeTabChangeEvent = jest.fn(); const refreshWorkflow = jest.fn(); const refreshOverviewAction = jest.fn(); @@ -52,17 +63,34 @@ describe('test AuditExecResultPanel', () => { beforeEach(() => { jest.useFakeTimers(); + MockDate.set('2025-10-18 12:00:00'); mockUseCurrentProject(); mockUseCurrentUser(); task.mockAllApi(); - mockUsePermission(undefined, { - mockSelector: true - }); + (useSelector as jest.Mock).mockImplementation((selector) => + selector({ + permission: { + moduleFeatureSupport: { + sqlOptimization: false, + knowledge: false + }, + userOperationPermissions: null + }, + sqlExecWorkflow: { + retryExecuteData: {}, + modalStatus: { + [ModalName.Sql_Exec_Workflow_Retry_Execute_Modal]: false + } + } + }) + ); + (useDispatch as jest.Mock).mockImplementation(() => dispatchSpy); }); afterEach(() => { jest.useRealTimers(); jest.clearAllMocks(); jest.clearAllTimers(); + MockDate.reset(); }); it('matches snapshot with default setup', () => { @@ -120,4 +148,126 @@ describe('test AuditExecResultPanel', () => { await act(async () => jest.advanceTimersByTime(3000)); expect(container).toMatchSnapshot(); }); + + it('should refresh workflow and overview info when emit Sql_Retry_Execute_Done event', async () => { + customRender(); + + act(() => { + EventEmitter.emit(EmitterKey.Sql_Retry_Execute_Done); + }); + + expect(refreshWorkflow).toHaveBeenCalledTimes(1); + expect(refreshOverviewAction).toHaveBeenCalledTimes(1); + }); + + it('should init modal status', async () => { + customRender(); + + await act(async () => jest.advanceTimersByTime(3000)); + + expect(dispatchSpy).toHaveBeenCalledTimes(1); + expect(dispatchSpy).toHaveBeenCalledWith({ + type: 'sqlExecWorkflow/initModalStatus', + payload: { + modalStatus: { + [ModalName.Sql_Exec_Workflow_Retry_Execute_Modal]: false + } + } + }); + }); + + describe('sql retry execute permission', () => { + it('should enable retry execute when task is failed and current time enable execute workflow', async () => { + const { container } = customRender({ + overviewList: { + list: [ + { + ...WorkflowTasksItemData[0], + status: GetWorkflowTasksItemV2StatusEnum.exec_failed + } + ], + total: 1 + }, + activeTabKey: WorkflowTasksItemData[0].task_id?.toString() + }); + await act(async () => jest.advanceTimersByTime(3000)); + expect(screen.getByText('再次执行')).toBeInTheDocument(); + expect(container).toMatchSnapshot(); + }); + + it('should disenable retry execute when task is exec_succeeded', async () => { + customRender({ + overviewList: { + list: [ + { + ...WorkflowTasksItemData[0], + status: GetWorkflowTasksItemV2StatusEnum.exec_succeeded + } + ], + total: 1 + }, + activeTabKey: WorkflowTasksItemData[0].task_id?.toString() + }); + await act(async () => jest.advanceTimersByTime(3000)); + expect(screen.queryByText('再次执行')).not.toBeInTheDocument(); + }); + + it('should disenable retry execute when current user is not in the assignee list', async () => { + mockUseCurrentUser({ + username: 'test' + }); + customRender({ + overviewList: { + list: [ + { + ...WorkflowTasksItemData[0], + status: GetWorkflowTasksItemV2StatusEnum.exec_failed + } + ], + total: 1 + }, + workflowInfo: { + ...WorkflowsOverviewListData, + record: { + ...WorkflowsOverviewListData.record, + workflow_step_list: [ + { + workflow_step_id: 24, + number: 3, + type: WorkflowStepResV2TypeEnum.sql_execute, + assignee_user_name_list: ['admin'], + state: WorkflowStepResV2StateEnum.approved + } + ] + } + }, + activeTabKey: WorkflowTasksItemData[0].task_id?.toString() + }); + await act(async () => jest.advanceTimersByTime(3000)); + expect(screen.queryByText('再次执行')).not.toBeInTheDocument(); + }); + + it('should disenable retry execute when current time is not in the maintenance time', async () => { + customRender({ + overviewList: { + list: [ + { + ...WorkflowTasksItemData[0], + status: GetWorkflowTasksItemV2StatusEnum.exec_failed, + instance_maintenance_times: [ + { + maintenance_start_time: { hour: 1, minute: 0 }, + maintenance_stop_time: { hour: 3, minute: 0 } + } + ] + } + ], + total: 1 + }, + activeTabKey: WorkflowTasksItemData[0].task_id?.toString() + }); + await act(async () => jest.advanceTimersByTime(3000)); + expect(screen.queryByText('再次执行')).not.toBeInTheDocument(); + }); + }); }); diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/hooks/useRetryExecute.ts b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/hooks/useRetryExecute.ts new file mode 100644 index 000000000..43d57034d --- /dev/null +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/hooks/useRetryExecute.ts @@ -0,0 +1,51 @@ +import { + IGetWorkflowTasksItemV2, + IWorkflowResV2 +} from '@actiontech/shared/lib/api/sqle/service/common'; +import { useMemo } from 'react'; +import { + GetWorkflowTasksItemV2StatusEnum, + WorkflowStepResV2TypeEnum +} from '@actiontech/shared/lib/api/sqle/service/common.enum'; +import { checkTimeInWithMaintenanceTime } from '../../../../Common/utils'; +import dayjs from 'dayjs'; +import { useCurrentUser } from '@actiontech/shared/lib/features'; + +interface IUseRetryExecuteParams { + currentTask?: IGetWorkflowTasksItemV2; + workflowInfo?: IWorkflowResV2; +} + +const useRetryExecute = (params: IUseRetryExecuteParams) => { + const { currentTask, workflowInfo } = params; + + const { username } = useCurrentUser(); + + const enableRetryExecute = useMemo(() => { + if (currentTask?.status !== GetWorkflowTasksItemV2StatusEnum.exec_failed) { + return false; + } + if ( + !workflowInfo?.record?.workflow_step_list + ?.find((v) => v.type === WorkflowStepResV2TypeEnum.sql_execute) + ?.assignee_user_name_list?.includes(username) + ) { + return false; + } + + if (currentTask?.instance_maintenance_times?.length) { + return checkTimeInWithMaintenanceTime( + dayjs(), + currentTask?.instance_maintenance_times + ); + } + + return true; + }, [currentTask, workflowInfo, username]); + + return { + enableRetryExecute + }; +}; + +export default useRetryExecute; diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/index.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/index.tsx index 552b2361a..d27310fcd 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/index.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/components/AuditExecResultPanel/index.tsx @@ -18,7 +18,7 @@ import { import { ToggleButtonStyleWrapper } from '../../../Common/style'; import DownloadRecord from '../../../Common/DownloadRecord'; import AuditResultFilterContainer from '../../../Common/AuditResultFilterContainer'; -import { useMemo } from 'react'; +import { useEffect, useMemo } from 'react'; import { getAuditTaskSQLsV2FilterExecStatusEnum } from '@actiontech/shared/lib/api/sqle/service/task/index.enum'; import { execStatusDictionary, @@ -28,12 +28,21 @@ import TaskResultList from './TaskResultList'; import useTaskResultSetup from './hooks/useTaskResultSetup'; import ListLayoutSelector from './ListLayoutSelector'; import WorkflowOverviewList from './OverviewList'; +import RetryExecuteModal from './RetryExecuteModal'; +import { useDispatch } from 'react-redux'; +import { initSqlExecWorkflowModalStatus } from '../../../../../store/sqlExecWorkflow/index'; +import { ModalName } from '../../../../../data/ModalName'; +import EmitterKey from '../../../../../data/EmitterKey'; +import EventEmitter from '../../../../../utils/EventEmitter'; +import useRetryExecute from './hooks/useRetryExecute'; + const AuditExecResultPanel: React.FC = ({ activeTabKey, taskInfos, ...resetProps }) => { const { t } = useTranslation(); + const dispatch = useDispatch(); const { getAuditLevelStatusSelectOptionValues } = useStaticStatus(); const { noDuplicate, @@ -78,6 +87,35 @@ const AuditExecResultPanel: React.FC = ({ resetProps.workflowInfo?.record?.current_step_number, resetProps.workflowInfo?.record?.workflow_step_list ]); + + const { enableRetryExecute } = useRetryExecute({ + currentTask: resetProps.overviewList?.list?.find( + (v) => v.task_id?.toString() === activeTabKey + ), + workflowInfo: resetProps.workflowInfo + }); + + useEffect(() => { + dispatch( + initSqlExecWorkflowModalStatus({ + modalStatus: { + [ModalName.Sql_Exec_Workflow_Retry_Execute_Modal]: false + } + }) + ); + }, [dispatch]); + + useEffect(() => { + const { unsubscribe } = EventEmitter.subscribe( + EmitterKey.Sql_Retry_Execute_Done, + () => { + resetProps.refreshWorkflow(); + resetProps.refreshOverviewAction(); + } + ); + return unsubscribe; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [resetProps.refreshWorkflow, resetProps.refreshOverviewAction]); return (
    {t('audit.result')}
    @@ -203,8 +241,10 @@ const AuditExecResultPanel: React.FC = ({ taskStatus={currentTask?.status} instanceName={currentTask?.instance_name} schema={currentTask?.instance_schema} + enableRetryExecute={enableRetryExecute} /> +
    ); }; diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/hooks/useAuditExecResultPanelSetup.ts b/packages/sqle/src/page/SqlExecWorkflow/Detail/hooks/useAuditExecResultPanelSetup.ts index 7f74bd1fe..b6bb1c9a2 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/hooks/useAuditExecResultPanelSetup.ts +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/hooks/useAuditExecResultPanelSetup.ts @@ -85,8 +85,7 @@ const useAuditExecResultPanelSetup = () => { }) ), { - ready: - !!urlParams.workflowId && activeTabKey === WORKFLOW_OVERVIEW_TAB_KEY, + ready: !!urlParams.workflowId, onSuccess: ({ list }) => { getOverviewListSuccessHandle?.(list ?? []); // 接口404时,错误重试次数 pollingErrorRetryCount无效 导致重复抛出404错误提示。所以如果当list不存在时也停止轮询 diff --git a/packages/sqle/src/page/SqlExecWorkflow/Detail/index.tsx b/packages/sqle/src/page/SqlExecWorkflow/Detail/index.tsx index 66140c34f..1832a14a3 100644 --- a/packages/sqle/src/page/SqlExecWorkflow/Detail/index.tsx +++ b/packages/sqle/src/page/SqlExecWorkflow/Detail/index.tsx @@ -21,6 +21,7 @@ import ModifySqlStatement from './components/ModifySqlStatement'; import useAuditExecResultPanelSetup from './hooks/useAuditExecResultPanelSetup'; import AuditExecResultPanel from './components/AuditExecResultPanel'; import SqlRollback from './components/SqlRollback'; + const SqlWorkflowDetail: React.FC = () => { const { username } = useCurrentUser(); const [ diff --git a/packages/sqle/src/store/sqlExecWorkflow/index.test.ts b/packages/sqle/src/store/sqlExecWorkflow/index.test.ts index 52573edee..f24e2a116 100644 --- a/packages/sqle/src/store/sqlExecWorkflow/index.test.ts +++ b/packages/sqle/src/store/sqlExecWorkflow/index.test.ts @@ -2,7 +2,8 @@ import reducers, { updateClonedExecWorkflowSqlAuditInfo, updateClonedExecWorkflowBaseInfo, updateVersionFirstStageInstances, - updateWorkflowRollbackSqlIds + updateWorkflowRollbackSqlIds, + updateRetryExecuteData } from '.'; import { AuditTaskResV1SqlSourceEnum, @@ -50,7 +51,9 @@ describe('store/sqlExecWorkflow', () => { clonedExecWorkflowSqlAuditInfo: null, clonedExecWorkflowBaseInfo: null, versionFirstStageInstances: null, - workflowRollbackSqlIds: null + workflowRollbackSqlIds: null, + retryExecuteData: null, + modalStatus: {} }, updateClonedExecWorkflowSqlAuditInfo(mockWorkflowSqlAuditInfo) ); @@ -58,7 +61,9 @@ describe('store/sqlExecWorkflow', () => { clonedExecWorkflowSqlAuditInfo: mockWorkflowSqlAuditInfo, clonedExecWorkflowBaseInfo: null, versionFirstStageInstances: null, - workflowRollbackSqlIds: null + workflowRollbackSqlIds: null, + retryExecuteData: null, + modalStatus: {} }); }); @@ -77,7 +82,9 @@ describe('store/sqlExecWorkflow', () => { clonedExecWorkflowSqlAuditInfo: null, clonedExecWorkflowBaseInfo: null, versionFirstStageInstances: null, - workflowRollbackSqlIds: null + workflowRollbackSqlIds: null, + retryExecuteData: null, + modalStatus: {} }, updateClonedExecWorkflowBaseInfo(mockWorkflowBaseInfo) ); @@ -85,7 +92,9 @@ describe('store/sqlExecWorkflow', () => { clonedExecWorkflowSqlAuditInfo: null, clonedExecWorkflowBaseInfo: mockWorkflowBaseInfo, versionFirstStageInstances: null, - workflowRollbackSqlIds: null + workflowRollbackSqlIds: null, + retryExecuteData: null, + modalStatus: {} }); }); @@ -108,7 +117,9 @@ describe('store/sqlExecWorkflow', () => { clonedExecWorkflowSqlAuditInfo: null, clonedExecWorkflowBaseInfo: null, versionFirstStageInstances: null, - workflowRollbackSqlIds: null + workflowRollbackSqlIds: null, + retryExecuteData: null, + modalStatus: {} }, updateVersionFirstStageInstances({ versionFirstStageInstances }) ); @@ -116,7 +127,9 @@ describe('store/sqlExecWorkflow', () => { clonedExecWorkflowSqlAuditInfo: null, clonedExecWorkflowBaseInfo: null, versionFirstStageInstances: versionFirstStageInstances, - workflowRollbackSqlIds: null + workflowRollbackSqlIds: null, + retryExecuteData: null, + modalStatus: {} }); }); @@ -132,7 +145,9 @@ describe('store/sqlExecWorkflow', () => { clonedExecWorkflowSqlAuditInfo: null, clonedExecWorkflowBaseInfo: null, versionFirstStageInstances: null, - workflowRollbackSqlIds: null + workflowRollbackSqlIds: null, + retryExecuteData: null, + modalStatus: {} }, updateWorkflowRollbackSqlIds({ workflowRollbackSqlIds }) ); @@ -140,7 +155,42 @@ describe('store/sqlExecWorkflow', () => { clonedExecWorkflowSqlAuditInfo: null, clonedExecWorkflowBaseInfo: null, versionFirstStageInstances: null, - workflowRollbackSqlIds: workflowRollbackSqlIds + workflowRollbackSqlIds: workflowRollbackSqlIds, + retryExecuteData: null, + modalStatus: {} + }); + }); + + test('should create action when call updateRetryExecuteData', () => { + const retryExecuteData = { + taskId: '1', + execSqlId: 1, + pageIndex: 1, + pageSize: 10 + }; + expect(updateRetryExecuteData({ ...retryExecuteData })).toEqual({ + payload: { ...retryExecuteData }, + type: 'sqlExecWorkflow/updateRetryExecuteData' + }); + + const state = reducers( + { + clonedExecWorkflowSqlAuditInfo: null, + clonedExecWorkflowBaseInfo: null, + versionFirstStageInstances: null, + workflowRollbackSqlIds: null, + retryExecuteData: null, + modalStatus: {} + }, + updateRetryExecuteData({ ...retryExecuteData }) + ); + expect(state).toEqual({ + clonedExecWorkflowSqlAuditInfo: null, + clonedExecWorkflowBaseInfo: null, + versionFirstStageInstances: null, + workflowRollbackSqlIds: null, + retryExecuteData: retryExecuteData, + modalStatus: {} }); }); }); diff --git a/packages/sqle/src/store/sqlExecWorkflow/index.ts b/packages/sqle/src/store/sqlExecWorkflow/index.ts index 3b2510c57..97e92f537 100644 --- a/packages/sqle/src/store/sqlExecWorkflow/index.ts +++ b/packages/sqle/src/store/sqlExecWorkflow/index.ts @@ -4,19 +4,30 @@ import { WorkflowBaseInfoFormFields } from '../../page/SqlExecWorkflow/Create/index.type'; import { IVersionStageInstance } from '@actiontech/shared/lib/api/sqle/service/common'; +import { commonModalReducer } from '../common'; +import { ModalStatus } from '@actiontech/shared/lib/types/common.type'; type SqlExecWorkflowReduxState = { clonedExecWorkflowSqlAuditInfo: SqlAuditInfoFormFields | null; clonedExecWorkflowBaseInfo: WorkflowBaseInfoFormFields | null; versionFirstStageInstances: IVersionStageInstance[] | null; workflowRollbackSqlIds: number[] | null; + retryExecuteData: { + taskId: string; + execSqlId?: number; + pageIndex?: number; + pageSize?: number; + } | null; + modalStatus: ModalStatus; }; const initialState: SqlExecWorkflowReduxState = { clonedExecWorkflowSqlAuditInfo: null, clonedExecWorkflowBaseInfo: null, versionFirstStageInstances: null, - workflowRollbackSqlIds: null + workflowRollbackSqlIds: null, + retryExecuteData: null, + modalStatus: {} }; const sqlExecWorkflow = createSlice({ name: 'sqlExecWorkflow', @@ -53,14 +64,26 @@ const sqlExecWorkflow = createSlice({ }> ) => { state.workflowRollbackSqlIds = workflowRollbackSqlIds; - } + }, + updateRetryExecuteData: ( + state, + { + payload: data + }: PayloadAction + ) => { + state.retryExecuteData = data; + }, + ...commonModalReducer() } }); export const { updateClonedExecWorkflowSqlAuditInfo, updateClonedExecWorkflowBaseInfo, updateVersionFirstStageInstances, - updateWorkflowRollbackSqlIds + updateWorkflowRollbackSqlIds, + updateRetryExecuteData, + initModalStatus: initSqlExecWorkflowModalStatus, + updateModalStatus: updateSqlExecWorkflowModalStatus } = sqlExecWorkflow.actions; export default sqlExecWorkflow.reducer;