Skip to content

Commit cedbf39

Browse files
[ui-importer] add tabs layout (#4203)
1 parent fe0789a commit cedbf39

File tree

17 files changed

+522
-146
lines changed

17 files changed

+522
-146
lines changed
Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@
1717
@use 'variables' as vars;
1818

1919
.antd.cuix {
20-
.hue-importer-preview-page {
20+
.hue-file-import-tabs {
2121
display: flex;
2222
flex-direction: column;
23-
gap: 16px;
24-
padding: 8px 24px 24px;
2523
height: 100%;
24+
overflow: auto;
25+
gap: vars.$cdl-spacing-s;
26+
padding: vars.$cdl-spacing-s vars.$cdl-spacing-m vars.$cdl-spacing-s vars.$cdl-spacing-m;
2627

2728
&__header {
2829
display: flex;
@@ -39,31 +40,29 @@
3940
&__actions {
4041
display: flex;
4142
align-items: center;
42-
gap: 24px;
43+
gap: vars.$cdl-spacing-m;
4344

4445
.action {
4546
margin-left: 10px;
4647
}
4748
}
4849
}
4950

50-
&__metadata {
51-
background-color: white;
52-
padding: 16px;
53-
font-size: 12px;
54-
color: #5a656d;
55-
}
56-
57-
&__main-section {
51+
&__tabs {
5852
display: flex;
59-
flex-direction: column;
60-
flex: 1;
61-
background-color: white;
62-
}
53+
height: 100%;
6354

64-
&__header-section {
65-
display: flex;
66-
justify-content: space-between;
55+
.ant-tabs-content-holder {
56+
display: flex;
57+
flex: 1;
58+
59+
.ant-tabs-content,
60+
.ant-tabs-tabpane-active {
61+
display: flex;
62+
flex-direction: column;
63+
flex: 1;
64+
}
65+
}
6766
}
6867
}
6968
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Licensed to Cloudera, Inc. under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. Cloudera, Inc. licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
import React from 'react';
18+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
19+
import '@testing-library/jest-dom';
20+
21+
import FileImportTabs from './FileImportTabs';
22+
import { FileMetaData, ImporterFileSource } from '../types';
23+
import useSaveData from '../../../utils/hooks/useSaveData/useSaveData';
24+
import useLoadData from '../../../utils/hooks/useLoadData/useLoadData';
25+
26+
jest.mock('../../../utils/hooks/useSaveData/useSaveData');
27+
const mockedUseSaveData = jest.mocked(useSaveData);
28+
29+
jest.mock('../../../utils/hooks/useLoadData/useLoadData');
30+
const mockedUseLoadData = jest.mocked(useLoadData);
31+
32+
jest.mock('../../../utils/hooks/useDataCatalog/useDataCatalog', () => ({
33+
useDataCatalog: jest.fn(() => ({
34+
loading: false,
35+
databases: ['default'],
36+
database: 'default',
37+
connectors: [{ id: 'hive', displayName: 'Hive' }],
38+
connector: { id: 'hive', displayName: 'Hive' },
39+
computes: [{ id: 'compute1', name: 'Compute 1' }],
40+
compute: { id: 'compute1', name: 'Compute 1' },
41+
setCompute: jest.fn(),
42+
setConnector: jest.fn(),
43+
setDatabase: jest.fn()
44+
}))
45+
}));
46+
47+
describe('FileImportTabs', () => {
48+
const mockFileMetaData: FileMetaData = {
49+
path: '/test/path/file.csv',
50+
fileName: 'file.csv',
51+
source: ImporterFileSource.LOCAL
52+
};
53+
54+
const defaultProps = {
55+
fileMetaData: mockFileMetaData
56+
};
57+
58+
beforeEach(() => {
59+
jest.clearAllMocks();
60+
61+
mockedUseSaveData.mockReturnValue({
62+
save: jest.fn(),
63+
loading: false
64+
});
65+
66+
mockedUseLoadData.mockReturnValue({
67+
data: undefined,
68+
loading: false,
69+
error: undefined,
70+
reloadData: jest.fn()
71+
});
72+
});
73+
74+
it('renders the component with header and tabs', async () => {
75+
render(<FileImportTabs {...defaultProps} />);
76+
77+
await waitFor(() => {
78+
expect(screen.getByText('file.csv')).toBeInTheDocument();
79+
expect(screen.getByText('Cancel')).toBeInTheDocument();
80+
expect(screen.getByText('Finish Import')).toBeInTheDocument();
81+
expect(screen.getByText('Preview')).toBeInTheDocument();
82+
expect(screen.getByText('Settings')).toBeInTheDocument();
83+
expect(screen.getByText('Partitions')).toBeInTheDocument();
84+
});
85+
});
86+
87+
it('renders FilePreviewTab in the Preview tab by default', async () => {
88+
render(<FileImportTabs {...defaultProps} />);
89+
90+
await waitFor(() => {
91+
expect(screen.getByText('Preview')).toBeInTheDocument();
92+
expect(screen.getByLabelText('Engine')).toBeInTheDocument();
93+
});
94+
});
95+
96+
it('starts with custom default active tab', async () => {
97+
render(<FileImportTabs {...defaultProps} defaultActiveKey="settings" />);
98+
99+
await waitFor(() => {
100+
expect(screen.getByText('Settings')).toBeInTheDocument();
101+
});
102+
});
103+
104+
it('has finish import button that calls save function', async () => {
105+
const mockSave = jest.fn();
106+
mockedUseSaveData.mockReturnValue({
107+
save: mockSave,
108+
loading: false
109+
});
110+
111+
render(<FileImportTabs {...defaultProps} />);
112+
113+
const finishButton = screen.getByText('Finish Import');
114+
fireEvent.click(finishButton);
115+
116+
await waitFor(() => {
117+
expect(mockSave).toHaveBeenCalledWith(expect.any(FormData));
118+
});
119+
});
120+
121+
it('shows loading state on finish import button', async () => {
122+
mockedUseSaveData.mockReturnValue({
123+
save: jest.fn(),
124+
loading: true
125+
});
126+
127+
render(<FileImportTabs {...defaultProps} />);
128+
129+
const finishButton = screen.getByText('Finish Import');
130+
await waitFor(() => {
131+
expect(finishButton.closest('button')).toHaveClass('ant-btn-loading');
132+
});
133+
});
134+
});
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Licensed to Cloudera, Inc. under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. Cloudera, Inc. licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
import React, { useState } from 'react';
18+
import { Tabs } from 'antd';
19+
import { i18nReact } from '../../../utils/i18nReact';
20+
import { DestinationConfig, FileMetaData } from '../types';
21+
22+
import './FileImportTabs.scss';
23+
import FilePreviewTab from '../FilePreviewTab/FilePreviewTab';
24+
import BorderlessButton from 'cuix/dist/components/Button/BorderlessButton';
25+
import { PrimaryButton } from 'cuix/dist/components/Button';
26+
import useSaveData from '../../../utils/hooks/useSaveData/useSaveData';
27+
import { FINISH_IMPORT_URL } from '../api';
28+
import { getDefaultTableName } from '../utils/utils';
29+
30+
interface FileImportTabsProps {
31+
fileMetaData: FileMetaData;
32+
onTabChange?: (activeKey: string) => void;
33+
defaultActiveKey?: string;
34+
}
35+
36+
const FileImportTabs = ({
37+
fileMetaData,
38+
defaultActiveKey = 'preview'
39+
}: FileImportTabsProps): JSX.Element => {
40+
const { t } = i18nReact.useTranslation();
41+
42+
const [destinationConfig, setDestinationConfig] = useState<DestinationConfig>({
43+
tableName: getDefaultTableName(fileMetaData)
44+
});
45+
46+
const handleDestinationConfigChange = (name: string, value: string) => {
47+
setDestinationConfig(prevConfig => ({
48+
...prevConfig,
49+
[name]: value
50+
}));
51+
};
52+
53+
const { save: finishImport, loading: finalizingImport } = useSaveData(FINISH_IMPORT_URL);
54+
55+
const handleFinishImport = () => {
56+
const source = {
57+
inputFormat: fileMetaData.source,
58+
path: fileMetaData.path,
59+
// format: fileFormat,
60+
sourceType: destinationConfig.connectorId
61+
};
62+
const destination = {
63+
outputFormat: 'table',
64+
nonDefaultLocation: fileMetaData.path,
65+
name: `${destinationConfig.database}.${destinationConfig.tableName}`,
66+
sourceType: destinationConfig.connectorId
67+
// columns: previewData?.columns
68+
};
69+
70+
const formData = new FormData();
71+
formData.append('source', JSON.stringify(source));
72+
formData.append('destination', JSON.stringify(destination));
73+
74+
finishImport(formData);
75+
};
76+
77+
const tabItems = [
78+
{
79+
label: t('Preview'),
80+
key: 'preview',
81+
children: (
82+
<FilePreviewTab
83+
fileMetaData={fileMetaData}
84+
destinationConfig={destinationConfig}
85+
onDestinationConfigChange={handleDestinationConfigChange}
86+
/>
87+
)
88+
},
89+
{
90+
label: t('Settings'),
91+
key: 'settings'
92+
// children: <SettingsTab />
93+
},
94+
{
95+
label: t('Partitions'),
96+
key: 'partitions'
97+
// children: <PartitionsTab />
98+
}
99+
];
100+
101+
return (
102+
<div className="hue-file-import-tabs">
103+
<div className="hue-file-import-tabs__header">
104+
<div className="hue-file-import-tabs__header__title">{fileMetaData?.fileName}</div>
105+
<div className="hue-file-import-tabs__header__actions">
106+
<BorderlessButton data-testid="hue-importer-preview-page__header__actions__cancel">
107+
{t('Cancel')}
108+
</BorderlessButton>
109+
<PrimaryButton loading={finalizingImport} onClick={handleFinishImport}>
110+
{t('Finish Import')}
111+
</PrimaryButton>
112+
</div>
113+
</div>
114+
<Tabs
115+
defaultActiveKey={defaultActiveKey}
116+
items={tabItems}
117+
className="hue-file-import-tabs__tabs"
118+
/>
119+
</div>
120+
);
121+
};
122+
123+
export default FileImportTabs;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ const DestinationSettings = ({
115115
};
116116

117117
const validateTableName = (name: string) => {
118-
const tableExists = tables.some(table => table.name.toLowerCase() === name.toLowerCase());
118+
const tableExists = tables?.some(table => table.name.toLowerCase() === name.toLowerCase());
119119
if (tableExists) {
120120
setError(prev => ({ ...prev, tableName: t('Table name already exists in the database') }));
121121
} else {
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Licensed to Cloudera, Inc. under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. Cloudera, Inc. licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
@use 'variables' as vars;
18+
19+
.antd.cuix {
20+
.hue-importer-preview-page {
21+
display: flex;
22+
flex-direction: column;
23+
gap: 16px;
24+
height: 100%;
25+
26+
&__metadata {
27+
background-color: vars.$cdl-white;
28+
padding: 16px;
29+
font-size: 12px;
30+
color: vars.$cdl-gray-700;
31+
}
32+
33+
&__main-section {
34+
display: flex;
35+
flex-direction: column;
36+
flex: 1;
37+
background-color: vars.$cdl-white;
38+
}
39+
40+
&__header-section {
41+
display: flex;
42+
justify-content: space-between;
43+
}
44+
}
45+
}

0 commit comments

Comments
 (0)