Skip to content

Commit 13676cf

Browse files
authored
Merge pull request #759 from actiontech/feature/sql-formatter
Feature/sql formatter
2 parents 1dad097 + 657e116 commit 13676cf

File tree

76 files changed

+6336
-716
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+6336
-716
lines changed

packages/base/src/hooks/useDbService/index.test.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,21 @@ describe('useDbService', () => {
133133
await screen.findAllByText('123123');
134134
expect(baseElementWithOptions).toMatchSnapshot();
135135
});
136+
137+
it('should return correct db_type when service id exists', async () => {
138+
const { result } = superRenderHook(() => useDbService(), {});
139+
140+
act(() => {
141+
result.current.updateDbServiceList({ project_uid: projectID });
142+
});
143+
await act(async () => jest.advanceTimersByTime(3000));
144+
145+
// Test with first service id
146+
const dbType1 = result.current.getServiceDbType('123123');
147+
expect(dbType1).toBe('MySQL');
148+
149+
// Test with second service id
150+
const dbType2 = result.current.getServiceDbType('300123');
151+
expect(dbType2).toBe('MySQL');
152+
});
136153
});

packages/base/src/hooks/useDbService/index.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useMemo } from 'react';
1+
import React, { useCallback, useMemo } from 'react';
22
import { useBoolean } from 'ahooks';
33
import { Select } from 'antd';
44
import { useDbServiceDriver } from '@actiontech/shared/lib/features';
@@ -112,14 +112,23 @@ const useDbService = () => {
112112
const dbServiceIDOptions = useMemo(() => {
113113
return generateCommonDbServiceOptions('id');
114114
}, [generateCommonDbServiceOptions]);
115+
116+
const getServiceDbType = useCallback(
117+
(id: string) => {
118+
return dbServiceList.find((i) => i.id === id)?.db_type ?? '';
119+
},
120+
[dbServiceList]
121+
);
122+
115123
return {
116124
dbServiceList,
117125
dbServiceOptions,
118126
loading,
119127
updateDbServiceList,
120128
generateDbServiceSelectOptions,
121129
generateDbServiceIDSelectOptions,
122-
dbServiceIDOptions
130+
dbServiceIDOptions,
131+
getServiceDbType
123132
};
124133
};
125134
export default useDbService;

packages/base/src/page/DataExportManagement/Create/__tests__/__snapshots__/index.test.tsx.snap

Lines changed: 129 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,33 @@ exports[`first should match snapshot when pageState is equal CREATE_TASK 1`] = `
359359
</div>
360360
</div>
361361
</div>
362+
<div
363+
class="ant-form-item css-1jlm9cy ant-form-item-hidden"
364+
>
365+
<div
366+
class="ant-row ant-form-item-row"
367+
>
368+
<div
369+
class="ant-col ant-col-11 ant-col-push-2 ant-form-item-control"
370+
>
371+
<div
372+
class="ant-form-item-control-input"
373+
>
374+
<div
375+
class="ant-form-item-control-input-content"
376+
>
377+
<input
378+
class="ant-input basic-input-wrapper css-1pd1cd5"
379+
id="dbType"
380+
placeholder="请输入{{name}}"
381+
type="text"
382+
value=""
383+
/>
384+
</div>
385+
</div>
386+
</div>
387+
</div>
388+
</div>
362389
<div
363390
class="ant-form-item has-required-style css-1jlm9cy"
364391
>
@@ -612,24 +639,114 @@ exports[`first should match snapshot when pageState is equal CREATE_TASK 1`] = `
612639
class="ant-form-item-control-input-content"
613640
>
614641
<div
615-
class="custom-monaco-editor-wrapper css-1esok7i"
642+
class="css-1sm8xw8"
616643
>
617-
<input
618-
aria-required="true"
619-
class="custom-monaco-editor"
620-
height="400px"
621-
id="sql"
622-
language="sql"
623-
options="{"automaticLayout":true,"minimap":{"enabled":false},"fontFamily":"SF Mono","fontSize":14,"fontWeight":"400","lineNumbersMinChars":2,"suggestFontSize":14,"scrollBeyondLastLine":false,"lineHeight":24,"letterSpacing":0.8,"overviewRulerBorder":false,"wordWrap":"on","wrappingStrategy":"advanced","wrappingIndent":"indent","scrollbar":{"vertical":"visible","horizontal":"visible","useShadows":false,"verticalScrollbarSize":12,"horizontalScrollbarSize":12,"alwaysConsumeMouseWheel":false},"folding":false,"lineNumbers":"on","glyphMargin":false,"lineDecorationsWidth":50,"renderValidationDecorations":"on","guides":{"indentation":true,"highlightActiveIndentation":true}}"
624-
value="/* input your sql */"
625-
width="100%"
626-
/>
644+
<div
645+
class="custom-monaco-editor-wrapper css-1esok7i"
646+
>
647+
<input
648+
aria-required="true"
649+
class="custom-monaco-editor"
650+
height="400px"
651+
id="sql"
652+
language="sql"
653+
options="{"automaticLayout":true,"minimap":{"enabled":false},"fontFamily":"SF Mono","fontSize":14,"fontWeight":"400","lineNumbersMinChars":2,"suggestFontSize":14,"scrollBeyondLastLine":false,"lineHeight":24,"letterSpacing":0.8,"overviewRulerBorder":false,"wordWrap":"on","wrappingStrategy":"advanced","wrappingIndent":"indent","scrollbar":{"vertical":"visible","horizontal":"visible","useShadows":false,"verticalScrollbarSize":12,"horizontalScrollbarSize":12,"alwaysConsumeMouseWheel":false},"folding":false,"lineNumbers":"on","glyphMargin":false,"lineDecorationsWidth":50,"renderValidationDecorations":"on","guides":{"indentation":true,"highlightActiveIndentation":true}}"
654+
value="/* input your sql */"
655+
width="100%"
656+
/>
657+
</div>
627658
</div>
628659
</div>
629660
</div>
630661
</div>
631662
</div>
632663
</div>
664+
<div
665+
class="ant-form-item css-15d6abf ant-form-item-hidden"
666+
>
667+
<div
668+
class="ant-row ant-form-item-row"
669+
>
670+
<div
671+
class="ant-col ant-form-item-label ant-form-item-label-left"
672+
>
673+
<label
674+
class="ant-form-item-no-colon"
675+
for="formatted"
676+
title=""
677+
/>
678+
</div>
679+
<div
680+
class="ant-col ant-form-item-control"
681+
>
682+
<div
683+
class="ant-form-item-control-input"
684+
>
685+
<div
686+
class="ant-form-item-control-input-content"
687+
>
688+
<button
689+
aria-checked="false"
690+
class="ant-switch basic-switch-wrapper css-g6dhbn"
691+
id="formatted"
692+
role="switch"
693+
type="button"
694+
>
695+
<div
696+
class="ant-switch-handle"
697+
/>
698+
<span
699+
class="ant-switch-inner"
700+
>
701+
<span
702+
class="ant-switch-inner-checked"
703+
/>
704+
<span
705+
class="ant-switch-inner-unchecked"
706+
/>
707+
</span>
708+
</button>
709+
</div>
710+
</div>
711+
</div>
712+
</div>
713+
</div>
714+
<div
715+
class="ant-form-item css-15d6abf ant-form-item-hidden"
716+
>
717+
<div
718+
class="ant-row ant-form-item-row"
719+
>
720+
<div
721+
class="ant-col ant-form-item-label ant-form-item-label-left"
722+
>
723+
<label
724+
class="ant-form-item-no-colon"
725+
for="originSql"
726+
title=""
727+
/>
728+
</div>
729+
<div
730+
class="ant-col ant-form-item-control"
731+
>
732+
<div
733+
class="ant-form-item-control-input"
734+
>
735+
<div
736+
class="ant-form-item-control-input-content"
737+
>
738+
<input
739+
class="ant-input basic-input-wrapper css-1pd1cd5"
740+
id="originSql"
741+
placeholder="请输入{{name}}"
742+
type="text"
743+
value=""
744+
/>
745+
</div>
746+
</div>
747+
</div>
748+
</div>
749+
</div>
633750
</section>
634751
</form>
635752
<section
@@ -657,7 +774,7 @@ exports[`first should match snapshot when pageState is equal CREATE_TASK 1`] = `
657774
style="margin-right: 12px;"
658775
>
659776
<button
660-
class="ant-btn ant-btn-default basic-button-wrapper css-geipcv"
777+
class="ant-btn ant-btn-default basic-button-wrapper css-1h90et6"
661778
type="button"
662779
>
663780
<span>

packages/base/src/page/DataExportManagement/Create/__tests__/index.test.tsx

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,22 @@ import CreateDataExport from '..';
66
import { CreateDataExportPageEnum } from '../../../../store/dataExport';
77
import { baseSuperRender } from '../../../../testUtils/superRender';
88
import { mockUseCreateDataExportReduxManage } from '../testUtils/mockUseCreateDataExportReduxManage';
9-
import { cleanup } from '@testing-library/react';
9+
import { cleanup, act, fireEvent, screen } from '@testing-library/react';
1010
import { ModalName } from 'sqle/src/data/ModalName';
1111
import { useDispatch, useSelector } from 'react-redux';
1212
import { mockUseCurrentUser } from '@actiontech/shared/lib/testUtil/mockHook/mockUseCurrentUser';
1313
import { mockUseCurrentProject } from '@actiontech/shared/lib/testUtil/mockHook/mockUseCurrentProject';
1414
import { mockUseDbServiceDriver } from '@actiontech/shared/lib/testUtil/mockHook/mockUseDbServiceDriver';
15+
import {
16+
getBySelector,
17+
queryBySelector
18+
} from '@actiontech/shared/lib/testUtil/customQuery';
19+
import dataExport from '@actiontech/shared/lib/testUtil/mockApi/base/dataExport';
20+
import dbServices from '@actiontech/shared/lib/testUtil/mockApi/base/dbServices';
21+
import instance from '@actiontech/shared/lib/testUtil/mockApi/sqle/instance';
22+
import { createSpySuccessResponse } from '@actiontech/shared/lib/testUtil/mockApi';
23+
import { mockProjectInfo } from '@actiontech/shared/lib/testUtil/mockHook/data';
24+
import { formatterSQL } from '@actiontech/dms-kit';
1525

1626
jest.mock('react-redux', () => {
1727
return {
@@ -24,6 +34,7 @@ jest.mock('react-redux', () => {
2434
describe('first', () => {
2535
const dispatchSpy = jest.fn();
2636
beforeEach(() => {
37+
jest.useFakeTimers();
2738
mockUseCurrentProject();
2839
mockUseCurrentUser();
2940
mockUseDbServiceDriver();
@@ -39,6 +50,12 @@ describe('first', () => {
3950
);
4051
(useDispatch as jest.Mock).mockImplementation(() => dispatchSpy);
4152
});
53+
afterEach(() => {
54+
jest.clearAllMocks();
55+
jest.clearAllTimers();
56+
jest.useRealTimers();
57+
cleanup();
58+
});
4259
ignoreConsoleErrors([
4360
UtilsConsoleErrorStringsEnum.INVALID_CSS_VALUE,
4461
UtilsConsoleErrorStringsEnum.UNKNOWN_EVENT_HANDLER
@@ -90,4 +107,107 @@ describe('first', () => {
90107

91108
expect(clearAllStateSpy).toHaveBeenCalledTimes(1);
92109
});
110+
111+
it('should use original SQL when database type does not support formatting', async () => {
112+
const addDataExportTaskSpy = dataExport.AddDataExportTask();
113+
const getDBServicesListSpy = dbServices.ListDBServicesTips();
114+
instance.getInstanceSchemas().mockImplementation(() =>
115+
createSpySuccessResponse({
116+
data: { schema_name_list: ['test_schema'] }
117+
})
118+
);
119+
const mockServiceData = {
120+
db_type: 'TiDB',
121+
host: '127.0.0.1',
122+
name: 'test',
123+
port: '3306',
124+
id: '123123'
125+
};
126+
// Mock TiDB 数据库服务(不支持 SQL 格式化)
127+
getDBServicesListSpy.mockImplementation(() =>
128+
createSpySuccessResponse({
129+
data: [mockServiceData]
130+
})
131+
);
132+
133+
const updatePageStateSpy = jest.fn();
134+
mockUseCreateDataExportReduxManage({
135+
updatePageState: updatePageStateSpy
136+
});
137+
138+
const { baseElement } = baseSuperRender(<CreateDataExport />);
139+
await act(async () => jest.advanceTimersByTime(3000));
140+
141+
// 填写工单名称
142+
const workflowNameInput = getBySelector('#workflow_subject', baseElement);
143+
fireEvent.change(workflowNameInput, {
144+
target: { value: 'test' }
145+
});
146+
await act(async () => jest.advanceTimersByTime(0));
147+
148+
// 选择数据源
149+
const dbServiceSelect = getBySelector('#dbService', baseElement);
150+
fireEvent.mouseDown(dbServiceSelect);
151+
await act(async () => jest.advanceTimersByTime(300));
152+
const dbServiceLabel = `${mockServiceData.name} (${mockServiceData.host}:${mockServiceData.port})`;
153+
fireEvent.click(
154+
getBySelector(`div[title="${dbServiceLabel}"]`, baseElement)
155+
);
156+
await act(async () => jest.advanceTimersByTime(3000));
157+
158+
// 选择 Schema
159+
160+
const SchemaNameEle = getBySelector('#schema', baseElement);
161+
fireEvent.mouseDown(SchemaNameEle);
162+
await act(async () => jest.advanceTimersByTime(0));
163+
fireEvent.click(getBySelector(`div[title="test_schema"]`));
164+
await act(async () => jest.advanceTimersByTime(3000));
165+
166+
// 输入原始 SQL(包含多余空格)
167+
const originalSql = 'SELECT * FROM users WHERE id=1';
168+
const sqlEditor = queryBySelector('.custom-monaco-editor', baseElement);
169+
if (sqlEditor) {
170+
fireEvent.input(sqlEditor, {
171+
target: { value: originalSql }
172+
});
173+
await act(async () => jest.advanceTimersByTime(100));
174+
}
175+
176+
// 点击 SQL 美化按钮
177+
const formatButton = screen.getByText('SQL美化');
178+
fireEvent.click(formatButton);
179+
await act(async () => jest.advanceTimersByTime(0));
180+
expect(sqlEditor).toHaveAttribute('value', formatterSQL(originalSql));
181+
// 验证显示只读提示信息
182+
fireEvent.click(formatButton);
183+
await act(async () => jest.advanceTimersByTime(0));
184+
expect(sqlEditor).toHaveAttribute('value', originalSql);
185+
186+
fireEvent.click(formatButton);
187+
await act(async () => jest.advanceTimersByTime(0));
188+
// 点击审核按钮
189+
const auditButton = screen.getByText('审 核');
190+
fireEvent.click(auditButton);
191+
await act(async () => jest.advanceTimersByTime(0));
192+
193+
await act(async () => jest.advanceTimersByTime(3000));
194+
195+
// 验证:提交时应该使用原始 SQL,而不是格式化后的 SQL
196+
expect(addDataExportTaskSpy).toHaveBeenCalledTimes(1);
197+
expect(addDataExportTaskSpy).toHaveBeenCalledWith({
198+
project_uid: mockProjectInfo.projectID,
199+
data_export_tasks: [
200+
{
201+
database_name: 'test_schema',
202+
db_service_uid: '123123',
203+
export_sql: originalSql // 应该使用原始 SQL,不是格式化后的
204+
}
205+
]
206+
});
207+
208+
expect(updatePageStateSpy).toHaveBeenCalledTimes(1);
209+
expect(updatePageStateSpy).toHaveBeenCalledWith(
210+
CreateDataExportPageEnum.SUBMIT_WORKFLOW
211+
);
212+
});
93213
});

0 commit comments

Comments
 (0)