diff --git a/packages/base/src/locale/en-US/dmsDataSource.ts b/packages/base/src/locale/en-US/dmsDataSource.ts index d16beb3752..d065f79a5a 100644 --- a/packages/base/src/locale/en-US/dmsDataSource.ts +++ b/packages/base/src/locale/en-US/dmsDataSource.ts @@ -55,6 +55,9 @@ export default { maintenanceTime: 'Maintenance time', maintenanceTimeTips: 'After setting the maintenance time, workflows can only be executed during this maintenance time period', + sqlWorkbenchMaintenanceTime: 'SQL workbench maintenance time', + sqlWorkbenchMaintenanceTimeTips: + 'Non-DQL statements in the SQL workbench can only run during these windows (independent from DB instance maintenance time)', needAuditSqlService: 'Enable SQL audit service', closeAuditSqlServiceTips: 'If you do not enable the SQL audit service, the DB instance cannot be used in SQL audit related services, are you sure to close it?', diff --git a/packages/base/src/locale/zh-CN/dmsDataSource.ts b/packages/base/src/locale/zh-CN/dmsDataSource.ts index fd781dfead..49ed14cbc5 100644 --- a/packages/base/src/locale/zh-CN/dmsDataSource.ts +++ b/packages/base/src/locale/zh-CN/dmsDataSource.ts @@ -71,6 +71,9 @@ export default { queryTimeoutSecond: 'SQL超时限制(s)', maintenanceTime: '运维时间', maintenanceTimeTips: '设置运维时间后,仅能在此运维时间段内上线工单', + sqlWorkbenchMaintenanceTime: 'SQL 工作台运维时间', + sqlWorkbenchMaintenanceTimeTips: + '设置后,非 DQL 语句仅能在该运维时间段内于 SQL 工作台执行(与数据源运维时间独立)', needAuditSqlService: '是否开启SQL审核业务', needAuditSqlServiceTips: '关闭后将禁用所用场景的SQL审核', closeAuditSqlServiceTips: diff --git a/packages/base/src/page/DataSource/components/AddDataSource/index.test.tsx b/packages/base/src/page/DataSource/components/AddDataSource/index.test.tsx index b0ce653ce2..68a725499a 100644 --- a/packages/base/src/page/DataSource/components/AddDataSource/index.test.tsx +++ b/packages/base/src/page/DataSource/components/AddDataSource/index.test.tsx @@ -63,7 +63,7 @@ describe('page/DataSource/AddDataSource', () => { afterEach(() => { jest.useRealTimers(); - jest.clearAllMocks(); + jest.restoreAllMocks(); cleanup(); }); @@ -219,7 +219,8 @@ describe('page/DataSource/AddDataSource', () => { audit_enabled: true, rule_template_id: '3', rule_template_name: 'default_MySQL1', - workflow_exec_enabled: true + workflow_exec_enabled: true, + maintenance_times: [] } }, user: 'root' @@ -443,7 +444,8 @@ describe('page/DataSource/AddDataSource', () => { sql_query_config: { allow_query_when_less_than_audit_level: undefined, audit_enabled: undefined, - workflow_exec_enabled: undefined + workflow_exec_enabled: undefined, + maintenance_times: [] } }, user: 'root', @@ -463,6 +465,114 @@ describe('page/DataSource/AddDataSource', () => { expect(navigateSpy).toHaveBeenCalledTimes(1); }); + it('should submit sql workbench maintenance times', async () => { + const { baseElement } = customRender(); + await act(async () => jest.advanceTimersByTime(9300)); + fireEvent.change(getBySelector('#name', baseElement), { + target: { + value: 'name-database-field-case' + } + }); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.mouseDown(getBySelector('#type', baseElement)); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.click(getBySelector('span[title="mysql"]', baseElement)); + await act(async () => jest.advanceTimersByTime(3000)); + fireEvent.change(getBySelector('#ip', baseElement), { + target: { + value: '1.1.1.3' + } + }); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.change(getBySelector('#user', baseElement), { + target: { + value: 'root' + } + }); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.change(getBySelector('#password', baseElement), { + target: { + value: 'root' + } + }); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.click(getBySelector('.editable-select-trigger', baseElement)); + await act(async () => jest.advanceTimersByTime(0)); + fireEvent.click(getAllBySelector('.ant-dropdown-menu-item')[0]); + await act(async () => jest.advanceTimersByTime(0)); + + fireEvent.mouseDown(getBySelector('#ruleTemplateName', baseElement)); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.click( + getBySelector('div[title="custom_template_b"]', baseElement) + ); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.mouseDown( + getBySelector('#dataExportRuleTemplateName', baseElement) + ); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.click(getAllBySelector('div[title="default_MySQL1"]')[1]); + await act(async () => jest.advanceTimersByTime(300)); + + fireEvent.click(getBySelector('#needAuditForSqlQuery', baseElement)); + await act(async () => jest.advanceTimersByTime(0)); + fireEvent.mouseDown(getBySelector('#workbenchTemplateName', baseElement)); + await act(async () => jest.advanceTimersByTime(0)); + fireEvent.click(getAllBySelector('div[title="default_MySQL1"]')[2]); + await act(async () => jest.advanceTimersByTime(0)); + fireEvent.mouseDown( + getBySelector('#allowQueryWhenLessThanAuditLevel', baseElement) + ); + await act(async () => jest.advanceTimersByTime(0)); + fireEvent.click(getBySelector('div[title="warn"]', baseElement)); + await act(async () => jest.advanceTimersByTime(0)); + + const workbenchMaintenanceField = screen + .getByText('SQL 工作台运维时间') + .closest('.ant-form-item') as HTMLElement; + fireEvent.click(getBySelector('button', workbenchMaintenanceField)); + await act(async () => jest.advanceTimersByTime(300)); + const inputEle = getAllBySelector('.ant-picker-input', baseElement); + expect(inputEle.length).toBe(2); + fireEvent.click(inputEle[0].parentElement!); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.click(screen.getAllByText('01')[0]); + await act(async () => jest.advanceTimersByTime(100)); + fireEvent.click(screen.getAllByText('05')[1]); + await act(async () => jest.advanceTimersByTime(100)); + fireEvent.click(getBySelector('.ant-picker-ok .ant-btn')); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.click(screen.getAllByText('03')[0]); + await act(async () => jest.advanceTimersByTime(100)); + fireEvent.click(screen.getAllByText('15')[1]); + await act(async () => jest.advanceTimersByTime(100)); + fireEvent.click(getBySelector('.ant-picker-ok .ant-btn')); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.click(screen.getByText('确 认')); + await act(async () => jest.advanceTimersByTime(300)); + + await act(async () => { + fireEvent.click(screen.getByText('提 交')); + }); + await act(async () => jest.advanceTimersByTime(3000)); + expect(requestAddDBServiceSpy).toHaveBeenCalled(); + expect( + requestAddDBServiceSpy.mock.calls[0][0].db_service.sqle_config + .sql_query_config.maintenance_times + ).toEqual([ + { + maintenance_start_time: { + hour: 1, + minute: 5 + }, + maintenance_stop_time: { + hour: 3, + minute: 15 + } + } + ]); + }); + it('render prepare api req', async () => { const requestRuleTemplateList = sqleMockApi.rule_template.getRuleTemplateTips(); diff --git a/packages/base/src/page/DataSource/components/AddDataSource/index.tsx b/packages/base/src/page/DataSource/components/AddDataSource/index.tsx index 150553f9d7..f571341043 100644 --- a/packages/base/src/page/DataSource/components/AddDataSource/index.tsx +++ b/packages/base/src/page/DataSource/components/AddDataSource/index.tsx @@ -57,7 +57,12 @@ const AddDataSource = () => { audit_enabled: values.needAuditForSqlQuery, rule_template_id: values.workbenchTemplateId, rule_template_name: values.workbenchTemplateName, - workflow_exec_enabled: values.allowExecuteNonDqlInWorkflow + workflow_exec_enabled: values.allowExecuteNonDqlInWorkflow, + maintenance_times: + values.sqlWorkbenchMaintenanceTime?.map((time) => ({ + maintenance_start_time: time.startTime, + maintenance_stop_time: time.endTime + })) ?? [] } }, // #endif diff --git a/packages/base/src/page/DataSource/components/Form/MaintenanceTimePicker/MaintenanceTimePicker.tsx b/packages/base/src/page/DataSource/components/Form/MaintenanceTimePicker/MaintenanceTimePicker.tsx index e10f0a95dd..e37dad6efa 100644 --- a/packages/base/src/page/DataSource/components/Form/MaintenanceTimePicker/MaintenanceTimePicker.tsx +++ b/packages/base/src/page/DataSource/components/Form/MaintenanceTimePicker/MaintenanceTimePicker.tsx @@ -10,7 +10,10 @@ import { BasicTag, EmptyBox } from '@actiontech/dms-kit'; -import { MaintenanceTimePickerPopoverWrapper } from './style'; +import { + MaintenanceTimePickerPopoverWrapper, + MaintenanceTimePickerTagsWrapper +} from './style'; const MaintenanceTimePicker: React.FC = (props) => { const { value = [], onChange } = props; const [messageApi, contextHolder] = message.useMessage(); @@ -76,17 +79,19 @@ const MaintenanceTimePicker: React.FC = (props) => { } > - {value.map((v, index) => ( - deleteTime(index)} - key={`${generateKey(v)}-${index}`} - size="large" - > - {addZero(v.startTime.hour)}:{addZero(v.startTime.minute)} - - {addZero(v.endTime.hour)}:{addZero(v.endTime.minute)} - - ))} + + {value.map((v, index) => ( + deleteTime(index)} + key={`${generateKey(v)}-${index}`} + size="small" + > + {addZero(v.startTime.hour)}:{addZero(v.startTime.minute)} -{' '} + {addZero(v.endTime.hour)}:{addZero(v.endTime.minute)} + + ))} + - - 01 - : - 05 - - - 05 - : - 05 - + + - +
+
+
+
+ +
+
+
+
+
+
+
+ + 请选择时间段 + +
+
+ +
+
+
+
+
+
+
diff --git a/packages/base/src/page/DataSource/components/Form/SqlAuditFields/index.tsx b/packages/base/src/page/DataSource/components/Form/SqlAuditFields/index.tsx index 00c792594d..417118f15d 100644 --- a/packages/base/src/page/DataSource/components/Form/SqlAuditFields/index.tsx +++ b/packages/base/src/page/DataSource/components/Form/SqlAuditFields/index.tsx @@ -20,6 +20,7 @@ import { SqlAuditFieldsSubTitleWrapper, DataSourceSqlAuditConfigurationStyleWrapper } from '../style'; +import MaintenanceTimePicker from '../MaintenanceTimePicker'; type SqlAuditFieldsValue = { needSqlAuditService: boolean; ruleTemplateId: string; @@ -251,6 +252,26 @@ const SqlAuditFields: React.FC = ({ })} + +
+ {t( + 'dmsDataSource.dataSourceForm.sqlWorkbenchMaintenanceTime' + )} +
+
+ {t( + 'dmsDataSource.dataSourceForm.sqlWorkbenchMaintenanceTimeTips' + )} +
+
+ } + name="sqlWorkbenchMaintenanceTime" + > + +
+
+
+
+ +
+
+
+
+
+
+
+ + 请选择时间段 + +
+
+ +
+
+
+
+
+
+
diff --git a/packages/base/src/page/DataSource/components/Form/index.tsx b/packages/base/src/page/DataSource/components/Form/index.tsx index 519405aa16..824a90c87f 100644 --- a/packages/base/src/page/DataSource/components/Form/index.tsx +++ b/packages/base/src/page/DataSource/components/Form/index.tsx @@ -103,7 +103,8 @@ const DataSourceForm: React.FC = (props) => { 'dataExportRuleTemplateId', 'workbenchTemplateName', 'workbenchTemplateId', - 'allowExecuteNonDqlInWorkflow' + 'allowExecuteNonDqlInWorkflow', + 'sqlWorkbenchMaintenanceTime' ]); // #endif // #if [sqle && ee] @@ -213,6 +214,13 @@ const DataSourceForm: React.FC = (props) => { allowExecuteNonDqlInWorkflow: !!props.defaultData.sqle_config?.sql_query_config ?.workflow_exec_enabled, + sqlWorkbenchMaintenanceTime: + props.defaultData.sqle_config?.sql_query_config?.maintenance_times?.map( + (item) => ({ + startTime: item.maintenance_start_time, + endTime: item.maintenance_stop_time + }) + ) ?? [], // #endif needUpdatePassword: false, environmentTagId: props.defaultData.environment_tag?.uid ?? '', diff --git a/packages/base/src/page/DataSource/components/Form/index.type.ts b/packages/base/src/page/DataSource/components/Form/index.type.ts index 830ca8427d..bd51e6ed2d 100644 --- a/packages/base/src/page/DataSource/components/Form/index.type.ts +++ b/packages/base/src/page/DataSource/components/Form/index.type.ts @@ -18,6 +18,7 @@ export type DataSourceFormField = { project: string; environmentTagId: string; maintenanceTime: MaintenanceTimeValue[]; + sqlWorkbenchMaintenanceTime?: MaintenanceTimeValue[]; needSqlAuditService?: boolean; ruleTemplateId?: string; ruleTemplateName?: string; diff --git a/packages/base/src/page/DataSource/components/UpdateDataSource/index.test.tsx b/packages/base/src/page/DataSource/components/UpdateDataSource/index.test.tsx index 6b752a416f..b41c606b67 100644 --- a/packages/base/src/page/DataSource/components/UpdateDataSource/index.test.tsx +++ b/packages/base/src/page/DataSource/components/UpdateDataSource/index.test.tsx @@ -240,6 +240,63 @@ describe('page/DataSource/UpdateDataSource', () => { expect(navigateSpy).toHaveBeenCalledWith(-1); }); + it('should update sql workbench maintenance times', async () => { + getListDBServicesSpy.mockImplementationOnce(() => + createSpySuccessResponse(mockDBListData) + ); + const { baseElement } = customRender(); + await act(async () => jest.advanceTimersByTime(9300)); + + // environment + fireEvent.click(getBySelector('.editable-select-trigger', baseElement)); + await act(async () => jest.advanceTimersByTime(0)); + fireEvent.click(getAllBySelector('.ant-dropdown-menu-item')[0]); + await act(async () => jest.advanceTimersByTime(0)); + + const workbenchMaintenanceField = screen + .getByText('SQL 工作台运维时间') + .closest('.ant-form-item') as HTMLElement; + fireEvent.click(getBySelector('button', workbenchMaintenanceField)); + await act(async () => jest.advanceTimersByTime(300)); + const inputEle = getAllBySelector('.ant-picker-input', baseElement); + expect(inputEle.length).toBe(2); + fireEvent.click(inputEle[0].parentElement!); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.click(screen.getAllByText('02')[0]); + await act(async () => jest.advanceTimersByTime(100)); + fireEvent.click(screen.getAllByText('10')[1]); + await act(async () => jest.advanceTimersByTime(100)); + fireEvent.click(getBySelector('.ant-picker-ok .ant-btn')); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.click(screen.getAllByText('04')[0]); + await act(async () => jest.advanceTimersByTime(100)); + fireEvent.click(screen.getAllByText('20')[1]); + await act(async () => jest.advanceTimersByTime(100)); + fireEvent.click(getBySelector('.ant-picker-ok .ant-btn')); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.click(screen.getByText('确 认')); + await act(async () => jest.advanceTimersByTime(300)); + + fireEvent.click(screen.getByText('提 交')); + await act(async () => jest.advanceTimersByTime(0)); + expect(updateDBServiceSpy).toHaveBeenCalledTimes(1); + const params = updateDBServiceSpy.mock.calls[0][0]; + expect( + params.db_service.sqle_config.sql_query_config.maintenance_times + ).toEqual([ + { + maintenance_start_time: { + hour: 2, + minute: 10 + }, + maintenance_stop_time: { + hour: 4, + minute: 20 + } + } + ]); + }); + it('render connectable modal when service can not connect', async () => { getListDBServicesSpy.mockImplementationOnce(() => createSpySuccessResponse(mockDBListData) diff --git a/packages/base/src/page/DataSource/components/UpdateDataSource/index.tsx b/packages/base/src/page/DataSource/components/UpdateDataSource/index.tsx index e39a5d59ab..44aae71d7e 100644 --- a/packages/base/src/page/DataSource/components/UpdateDataSource/index.tsx +++ b/packages/base/src/page/DataSource/components/UpdateDataSource/index.tsx @@ -62,7 +62,12 @@ const UpdateDataSource = () => { audit_enabled: values.needAuditForSqlQuery, rule_template_id: values.workbenchTemplateId, rule_template_name: values.workbenchTemplateName, - workflow_exec_enabled: values.allowExecuteNonDqlInWorkflow + workflow_exec_enabled: values.allowExecuteNonDqlInWorkflow, + maintenance_times: + values.sqlWorkbenchMaintenanceTime?.map((time) => ({ + maintenance_start_time: time.startTime, + maintenance_stop_time: time.endTime + })) ?? [] } }, // #endif diff --git a/packages/base/src/page/SyncDataSource/AddPage/__snapshots__/index.test.tsx.snap b/packages/base/src/page/SyncDataSource/AddPage/__snapshots__/index.test.tsx.snap index 0213b95fa5..2599472447 100644 --- a/packages/base/src/page/SyncDataSource/AddPage/__snapshots__/index.test.tsx.snap +++ b/packages/base/src/page/SyncDataSource/AddPage/__snapshots__/index.test.tsx.snap @@ -1264,6 +1264,78 @@ exports[`page/SyncDataSource/AddPage render add submit for success 1`] = `
+
+
+
+ +
+
+
+
+
+
+
+ + 请选择时间段 + +
+
+ +
+
+
+
+
+
+
diff --git a/packages/base/src/page/SyncDataSource/AddPage/index.test.tsx b/packages/base/src/page/SyncDataSource/AddPage/index.test.tsx index 05e7ba7d5c..44fefd0461 100644 --- a/packages/base/src/page/SyncDataSource/AddPage/index.test.tsx +++ b/packages/base/src/page/SyncDataSource/AddPage/index.test.tsx @@ -177,7 +177,8 @@ describe('page/SyncDataSource/AddPage', () => { audit_enabled: true, allow_query_when_less_than_audit_level: 'notice', rule_template_id: '9', - rule_template_name: 'custom_template' + rule_template_name: 'custom_template', + maintenance_times: [] } }, cron_express: '0 0 * * *', @@ -197,4 +198,104 @@ describe('page/SyncDataSource/AddPage', () => { fireEvent.click(screen.getByText('关 闭')); await act(async () => jest.advanceTimersByTime(300)); }); + + it('should submit sql workbench maintenance times', async () => { + const requestSubmit = syncTaskList.addTaskSource(); + const { baseElement } = customRender(); + await act(async () => jest.advanceTimersByTime(3000)); + + fireEvent.change(getBySelector('#name', baseElement), { + target: { + value: 'name-sync-maintenance' + } + }); + fireEvent.mouseDown(getBySelector('#source', baseElement)); + fireEvent.click(getBySelector('div[title="source1"]', baseElement)); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.change(getBySelector('#params_key1', baseElement), { + target: { value: 'param1' } + }); + fireEvent.change(getBySelector('#params_key2', baseElement), { + target: { value: 111 } + }); + fireEvent.change(getBySelector('#url', baseElement), { + target: { + value: 'http://192.168.1.1:27601' + } + }); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.mouseDown(getBySelector('#instanceType', baseElement)); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.click(getBySelector('span[title="mysql"]', baseElement)); + await act(async () => jest.advanceTimersByTime(3000)); + + fireEvent.mouseDown(getBySelector('#ruleTemplateName', baseElement)); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.click( + getBySelector('div[title="custom_template_b"]', baseElement) + ); + fireEvent.mouseDown( + getBySelector('#dataExportRuleTemplateName', baseElement) + ); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.click(getAllBySelector('div[title="custom_template"]')[1]); + await act(async () => jest.advanceTimersByTime(300)); + + fireEvent.click(getBySelector('#needAuditForSqlQuery', baseElement)); + await act(async () => jest.advanceTimersByTime(0)); + fireEvent.mouseDown(getBySelector('#workbenchTemplateName', baseElement)); + await act(async () => jest.advanceTimersByTime(0)); + fireEvent.click(getAllBySelector('div[title="custom_template"]')[2]); + await act(async () => jest.advanceTimersByTime(0)); + fireEvent.mouseDown( + getBySelector('#allowQueryWhenLessThanAuditLevel', baseElement) + ); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.click(getBySelector('div[title="notice"]', baseElement)); + await act(async () => jest.advanceTimersByTime(300)); + + const workbenchMaintenanceField = screen + .getByText('SQL 工作台运维时间') + .closest('.ant-form-item') as HTMLElement; + fireEvent.click(getBySelector('button', workbenchMaintenanceField)); + await act(async () => jest.advanceTimersByTime(300)); + const inputEle = getAllBySelector('.ant-picker-input', baseElement); + expect(inputEle.length).toBe(2); + fireEvent.click(inputEle[0].parentElement!); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.click(screen.getAllByText('01')[0]); + await act(async () => jest.advanceTimersByTime(100)); + fireEvent.click(screen.getAllByText('05')[1]); + await act(async () => jest.advanceTimersByTime(100)); + fireEvent.click(getBySelector('.ant-picker-ok .ant-btn')); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.click(screen.getAllByText('03')[0]); + await act(async () => jest.advanceTimersByTime(100)); + fireEvent.click(screen.getAllByText('15')[1]); + await act(async () => jest.advanceTimersByTime(100)); + fireEvent.click(getBySelector('.ant-picker-ok .ant-btn')); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.click(screen.getByText('确 认')); + await act(async () => jest.advanceTimersByTime(300)); + + fireEvent.click(screen.getByText('提 交')); + await act(async () => jest.advanceTimersByTime(3000)); + + expect(requestSubmit).toHaveBeenCalled(); + expect( + requestSubmit.mock.calls[0][0].db_service_sync_task?.sqle_config + ?.sql_query_config?.maintenance_times + ).toEqual([ + { + maintenance_start_time: { + hour: 1, + minute: 5 + }, + maintenance_stop_time: { + hour: 3, + minute: 15 + } + } + ]); + }); }); diff --git a/packages/base/src/page/SyncDataSource/AddPage/index.tsx b/packages/base/src/page/SyncDataSource/AddPage/index.tsx index a447ce1f2b..4314e7bf0b 100644 --- a/packages/base/src/page/SyncDataSource/AddPage/index.tsx +++ b/packages/base/src/page/SyncDataSource/AddPage/index.tsx @@ -57,7 +57,12 @@ const AddSyncTask: React.FC = () => { allow_query_when_less_than_audit_level: values.allowQueryWhenLessThanAuditLevel, rule_template_id: values.workbenchTemplateId, - rule_template_name: values.workbenchTemplateName + rule_template_name: values.workbenchTemplateName, + maintenance_times: + values.sqlWorkbenchMaintenanceTime?.map((time) => ({ + maintenance_start_time: time.startTime, + maintenance_stop_time: time.endTime + })) ?? [] } }, // #endif diff --git a/packages/base/src/page/SyncDataSource/Form/index.tsx b/packages/base/src/page/SyncDataSource/Form/index.tsx index 5b993d0555..704c3b727b 100644 --- a/packages/base/src/page/SyncDataSource/Form/index.tsx +++ b/packages/base/src/page/SyncDataSource/Form/index.tsx @@ -56,7 +56,8 @@ const SyncTaskForm: React.FC = ({ 'dataExportRuleTemplateId', 'dataExportRuleTemplateName', 'workbenchTemplateId', - 'workbenchTemplateName' + 'workbenchTemplateName', + 'sqlWorkbenchMaintenanceTime' ]); updateGlobalRuleTemplateList(type); // #endif @@ -121,6 +122,7 @@ const SyncTaskForm: React.FC = ({ 'workbenchTemplateId', 'workbenchTemplateName', 'allowQueryWhenLessThanAuditLevel', + 'sqlWorkbenchMaintenanceTime', // #endif 'syncInterval' ]); @@ -166,6 +168,13 @@ const SyncTaskForm: React.FC = ({ allowQueryWhenLessThanAuditLevel: defaultValue.sqle_config?.sql_query_config ?.allow_query_when_less_than_audit_level, + sqlWorkbenchMaintenanceTime: + defaultValue.sqle_config?.sql_query_config?.maintenance_times?.map( + (item) => ({ + startTime: item.maintenance_start_time, + endTime: item.maintenance_stop_time + }) + ) ?? [], // #endif syncInterval: defaultValue.cron_express, params: generateFormValueByParams(defaultValue.additional_params ?? []) diff --git a/packages/base/src/page/SyncDataSource/Form/index.type.ts b/packages/base/src/page/SyncDataSource/Form/index.type.ts index 42cb042ef8..81e5529339 100644 --- a/packages/base/src/page/SyncDataSource/Form/index.type.ts +++ b/packages/base/src/page/SyncDataSource/Form/index.type.ts @@ -4,6 +4,7 @@ import { IGetDBServiceSyncTask } from '@actiontech/shared/lib/api/base/service/c import { BackendFormValues } from '@actiontech/shared'; import { SQLQueryConfigAllowQueryWhenLessThanAuditLevelEnum } from '@actiontech/shared/lib/api/base/service/common.enum'; import useTaskSource from '../../../hooks/useTaskSource'; +import { MaintenanceTimeValue } from '../../DataSource/components/Form/MaintenanceTimePicker'; export type SyncTaskFormFields = { name: string; @@ -21,6 +22,7 @@ export type SyncTaskFormFields = { workbenchTemplateName?: string; dataExportRuleTemplateId?: string; dataExportRuleTemplateName?: string; + sqlWorkbenchMaintenanceTime?: MaintenanceTimeValue[]; }; export type SyncTaskFormProps = { diff --git a/packages/base/src/page/SyncDataSource/UpdatePage/__snapshots__/index.test.tsx.snap b/packages/base/src/page/SyncDataSource/UpdatePage/__snapshots__/index.test.tsx.snap index 57f0a6ab7a..0405d13123 100644 --- a/packages/base/src/page/SyncDataSource/UpdatePage/__snapshots__/index.test.tsx.snap +++ b/packages/base/src/page/SyncDataSource/UpdatePage/__snapshots__/index.test.tsx.snap @@ -3294,6 +3294,78 @@ exports[`page/SyncDataSource/UpdateSyncTask render click reset btn 2`] = `
+
+
+
+ +
+
+
+
+
+
+
+ + 请选择时间段 + +
+
+ +
+
+
+
+
+
+
@@ -4703,6 +4775,78 @@ exports[`page/SyncDataSource/UpdateSyncTask render edit database snap 1`] = `
+
+
+
+ +
+
+
+
+
+
+
+ + 请选择时间段 + +
+
+ +
+
+
+
+
+
+
@@ -6445,6 +6589,78 @@ exports[`page/SyncDataSource/UpdateSyncTask render update task submit 1`] = `
+
+
+
+ +
+
+
+
+
+
+
+ + 请选择时间段 + +
+
+ +
+
+
+
+
+
+
diff --git a/packages/base/src/page/SyncDataSource/UpdatePage/index.test.tsx b/packages/base/src/page/SyncDataSource/UpdatePage/index.test.tsx index 6d1b00203a..b643ceb19e 100644 --- a/packages/base/src/page/SyncDataSource/UpdatePage/index.test.tsx +++ b/packages/base/src/page/SyncDataSource/UpdatePage/index.test.tsx @@ -8,7 +8,10 @@ import EventEmitter from '../../../utils/EventEmitter'; import UpdateSyncTask from '.'; import { createSpySuccessResponse } from '@actiontech/shared/lib/testUtil/mockApi'; import { DataSourceManagerSegmentedKey } from '../../DataSourceManagement/index.type'; -import { getBySelector } from '@actiontech/shared/lib/testUtil/customQuery'; +import { + getBySelector, + getAllBySelector +} from '@actiontech/shared/lib/testUtil/customQuery'; import { syncTaskDetailMockData } from '@actiontech/shared/lib/testUtil/mockApi/base/syncTaskList/data'; jest.mock('react-router-dom', () => { @@ -138,7 +141,8 @@ describe('page/SyncDataSource/UpdateSyncTask', () => { audit_enabled: undefined, rule_template_id: undefined, rule_template_name: undefined, - allow_query_when_less_than_audit_level: undefined + allow_query_when_less_than_audit_level: undefined, + maintenance_times: [] } }, additional_params: [ @@ -158,6 +162,62 @@ describe('page/SyncDataSource/UpdateSyncTask', () => { ); }); + it('should submit sql workbench maintenance times on update', async () => { + const requestUpdate = syncTaskList.updateTaskSource(); + const { container, baseElement } = customRender(); + await act(async () => jest.advanceTimersByTime(3300)); + + fireEvent.change(getBySelector('#url', container), { + target: { + value: 'http://192.168.1.5:5555' + } + }); + + const workbenchMaintenanceField = screen + .getByText('SQL 工作台运维时间') + .closest('.ant-form-item') as HTMLElement; + fireEvent.click(getBySelector('button', workbenchMaintenanceField)); + await act(async () => jest.advanceTimersByTime(300)); + const inputEle = getAllBySelector('.ant-picker-input', baseElement); + expect(inputEle.length).toBe(2); + fireEvent.click(inputEle[0].parentElement!); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.click(screen.getAllByText('02')[0]); + await act(async () => jest.advanceTimersByTime(100)); + fireEvent.click(screen.getAllByText('10')[1]); + await act(async () => jest.advanceTimersByTime(100)); + fireEvent.click(getBySelector('.ant-picker-ok .ant-btn')); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.click(screen.getAllByText('04')[0]); + await act(async () => jest.advanceTimersByTime(100)); + fireEvent.click(screen.getAllByText('20')[1]); + await act(async () => jest.advanceTimersByTime(100)); + fireEvent.click(getBySelector('.ant-picker-ok .ant-btn')); + await act(async () => jest.advanceTimersByTime(300)); + fireEvent.click(screen.getByText('确 认')); + await act(async () => jest.advanceTimersByTime(300)); + + fireEvent.click(screen.getByText('提 交')); + await act(async () => jest.advanceTimersByTime(3000)); + + expect(requestUpdate).toHaveBeenCalled(); + expect( + requestUpdate.mock.calls[0][0].db_service_sync_task?.sqle_config + ?.sql_query_config?.maintenance_times + ).toEqual([ + { + maintenance_start_time: { + hour: 2, + minute: 10 + }, + maintenance_stop_time: { + hour: 4, + minute: 20 + } + } + ]); + }); + it('get task source failed', async () => { const requestDetail = syncTaskList.getTaskSource(); requestDetail.mockImplementation(() => diff --git a/packages/base/src/page/SyncDataSource/UpdatePage/index.tsx b/packages/base/src/page/SyncDataSource/UpdatePage/index.tsx index 48df1ee21a..ef50ba61a7 100644 --- a/packages/base/src/page/SyncDataSource/UpdatePage/index.tsx +++ b/packages/base/src/page/SyncDataSource/UpdatePage/index.tsx @@ -63,7 +63,12 @@ const UpdateSyncTask: React.FC = () => { allow_query_when_less_than_audit_level: values.allowQueryWhenLessThanAuditLevel, rule_template_id: values.workbenchTemplateId, - rule_template_name: values.workbenchTemplateName + rule_template_name: values.workbenchTemplateName, + maintenance_times: + values.sqlWorkbenchMaintenanceTime?.map((time) => ({ + maintenance_start_time: time.startTime, + maintenance_stop_time: time.endTime + })) ?? [] } }, // #endif diff --git a/packages/shared/lib/api/base/service/common.d.ts b/packages/shared/lib/api/base/service/common.d.ts index 8ed70b796d..6eb1f79d70 100644 --- a/packages/shared/lib/api/base/service/common.d.ts +++ b/packages/shared/lib/api/base/service/common.d.ts @@ -1193,7 +1193,9 @@ export interface IGlobalDataExportWorkflow { workflow_uid?: string; } -export interface II18nStr {} +export interface II18nStr { + [key: string]: string; +} export interface IImportDBService { additional_params?: IAdditionalParam[]; @@ -2536,6 +2538,8 @@ export interface ISQLQueryConfig { audit_enabled?: boolean; + maintenance_times?: IMaintenanceTime[]; + max_pre_query_rows?: number; query_timeout_second?: number; diff --git a/packages/sqle/src/page/RuleKnowledge/Common/MarkdownPreview/markdownPreviewOptions.tsx b/packages/sqle/src/page/RuleKnowledge/Common/MarkdownPreview/markdownPreviewOptions.tsx index 9499ae9bc3..049c13cec9 100644 --- a/packages/sqle/src/page/RuleKnowledge/Common/MarkdownPreview/markdownPreviewOptions.tsx +++ b/packages/sqle/src/page/RuleKnowledge/Common/MarkdownPreview/markdownPreviewOptions.tsx @@ -3,9 +3,11 @@ import { isArray } from 'lodash'; import LabelPreview from './components/LabelPreview'; import SqlDiffPreview from './components/SqlDiffPreview'; import SqlPreview from './components/SqlPreview'; -import { MDEditorProps } from '@uiw/react-md-editor'; +import type { BasicMDEditorProps } from '@actiontech/shared'; -export const markdownPreviewOptions: MDEditorProps['previewOptions'] = { +type PreviewOptions = BasicMDEditorProps['previewOptions']; + +export const markdownPreviewOptions: PreviewOptions = { components: { code: ({ children, className, ...props }) => { /**