Skip to content

Commit 6bb1aad

Browse files
[ui-importer] add configure setting feature in file preview (#4101)
1 parent b73ba9d commit 6bb1aad

File tree

6 files changed

+241
-4
lines changed

6 files changed

+241
-4
lines changed

desktop/core/src/desktop/js/apps/newimporter/ImporterFilePreview/ImporterFilePreview.scss

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
display: flex;
2222
flex-direction: column;
2323
gap: 16px;
24-
padding: 8px 16px 16px 24px;
24+
padding: 8px 24px 24px;
2525
height: 100%;
2626

2727
&__header {
@@ -57,10 +57,9 @@
5757

5858
&__main-section {
5959
display: flex;
60+
flex-direction: column;
6061
flex: 1;
61-
justify-content: center;
6262
background-color: white;
63-
padding: 16px 0 0 0;
6463
}
6564
}
6665
}

desktop/core/src/desktop/js/apps/newimporter/ImporterFilePreview/ImporterFilePreview.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { i18nReact } from '../../../utils/i18nReact';
2727
import { BorderlessButton, PrimaryButton } from 'cuix/dist/components/Button';
2828
import PaginatedTable from '../../../reactComponents/PaginatedTable/PaginatedTable';
2929
import { GUESS_FORMAT_URL, GUESS_FIELD_TYPES_URL } from '../api';
30+
import SourceConfiguration from './SourceConfiguration/SourceConfiguration';
3031

3132
import './ImporterFilePreview.scss';
3233

@@ -36,7 +37,7 @@ interface ImporterFilePreviewProps {
3637

3738
const ImporterFilePreview = ({ fileMetaData }: ImporterFilePreviewProps): JSX.Element => {
3839
const { t } = i18nReact.useTranslation();
39-
const [fileFormat, setFileFormat] = useState<FileFormatResponse | null>(null);
40+
const [fileFormat, setFileFormat] = useState<FileFormatResponse | undefined>();
4041

4142
const { save: guessFormat, loading: guessingFormat } = useSaveData<FileFormatResponse>(
4243
GUESS_FORMAT_URL,
@@ -97,6 +98,7 @@ const ImporterFilePreview = ({ fileMetaData }: ImporterFilePreviewProps): JSX.El
9798
</div>
9899
<div className="hue-importer-preview-page__metadata">{t('DESTINATION')}</div>
99100
<div className="hue-importer-preview-page__main-section">
101+
<SourceConfiguration fileFormat={fileFormat} setFileFormat={setFileFormat} />
100102
<PaginatedTable<ImporterTableData>
101103
loading={guessingFormat || guessingFields}
102104
data={tableData}
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-configuration {
21+
padding: 16px;
22+
23+
&__summary {
24+
display: flex;
25+
gap: 8px;
26+
width: max-content;
27+
list-style: none;
28+
color: vars.$fluidx-blue-600;
29+
cursor: pointer;
30+
}
31+
32+
&__dropdown {
33+
border: 1px solid vars.$fluidx-gray-600;
34+
border-radius: vars.$border-radius-base;
35+
width: 100%;
36+
}
37+
}
38+
39+
.hue-importer-configuration-options {
40+
padding-top: 16px;
41+
display: grid;
42+
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
43+
grid-gap: 16px;
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from 'react';
2+
import { render, fireEvent, waitFor } from '@testing-library/react';
3+
import userEvent from '@testing-library/user-event';
4+
import '@testing-library/jest-dom';
5+
import SourceConfiguration from './SourceConfiguration';
6+
import { FileFormatResponse } from '../../types';
7+
import { separator } from '../../constants';
8+
9+
describe('SourceConfiguration Component', () => {
10+
const mockSetFileFormat = jest.fn();
11+
const mockFileFormat: FileFormatResponse = {
12+
quoteChar: '"',
13+
recordSeparator: '\\n',
14+
type: 'csv',
15+
hasHeader: true,
16+
fieldSeparator: ',',
17+
status: 0
18+
};
19+
20+
beforeEach(() => {
21+
jest.clearAllMocks();
22+
});
23+
24+
it('should render the component', () => {
25+
const { getByText, getAllByRole } = render(
26+
<SourceConfiguration fileFormat={mockFileFormat} setFileFormat={mockSetFileFormat} />
27+
);
28+
expect(getByText('Configure source')).toBeInTheDocument();
29+
expect(getAllByRole('combobox')).toHaveLength(5);
30+
});
31+
32+
it('calls setFileFormat on option change', async () => {
33+
const { getByText, getAllByRole } = render(
34+
<SourceConfiguration fileFormat={mockFileFormat} setFileFormat={mockSetFileFormat} />
35+
);
36+
37+
const selectElement = getAllByRole('combobox')[0];
38+
await userEvent.click(selectElement);
39+
fireEvent.click(getByText(separator[3].label));
40+
41+
await waitFor(() =>
42+
expect(mockSetFileFormat).toHaveBeenCalledWith({
43+
...mockFileFormat,
44+
fieldSeparator: separator[3].value
45+
})
46+
);
47+
});
48+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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, { useCallback } from 'react';
18+
import Select from 'cuix/dist/components/Select/Select';
19+
import ConfigureIcon from '@cloudera/cuix-core/icons/react/ConfigureIcon';
20+
import { i18nReact } from '../../../../utils/i18nReact';
21+
import { sourceConfigs } from '../../constants';
22+
import { FileFormatResponse } from '../../types';
23+
24+
import './SourceConfiguration.scss';
25+
26+
interface SourceConfigurationProps {
27+
fileFormat?: FileFormatResponse;
28+
setFileFormat: (format: FileFormatResponse) => void;
29+
}
30+
const SourceConfiguration = ({
31+
fileFormat,
32+
setFileFormat
33+
}: SourceConfigurationProps): JSX.Element => {
34+
const { t } = i18nReact.useTranslation();
35+
36+
const onChange = useCallback(
37+
(value: string | number | boolean, name: keyof FileFormatResponse) => {
38+
if (fileFormat) {
39+
setFileFormat({
40+
...fileFormat,
41+
[name]: value
42+
});
43+
}
44+
},
45+
[fileFormat, setFileFormat]
46+
);
47+
48+
return (
49+
<details className="hue-importer-configuration">
50+
<summary className="hue-importer-configuration__summary">
51+
<ConfigureIcon />
52+
{t('Configure source')}
53+
</summary>
54+
<div className="hue-importer-configuration-options">
55+
{sourceConfigs.map(config => (
56+
<div key={config.name}>
57+
<label htmlFor={config.name}>{t(config.label)}</label>
58+
<Select
59+
bordered={true}
60+
className="hue-importer-configuration__dropdown"
61+
id={config.name}
62+
options={config.options}
63+
onChange={value => onChange(value, config.name)}
64+
value={fileFormat?.[config.name]}
65+
getPopupContainer={triggerNode => triggerNode.parentElement}
66+
/>
67+
</div>
68+
))}
69+
</div>
70+
</details>
71+
);
72+
};
73+
74+
export default SourceConfiguration;
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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 { FileFormatResponse } from './types';
18+
19+
export const separator = [
20+
{ value: ',', label: 'Comma (,)' },
21+
{ value: '\\t', label: '^Tab (\\t)' },
22+
{ value: '\\n', label: '^New Line (\\n)' },
23+
{ value: '|', label: 'Pipe (|)' },
24+
{ value: '"', label: 'Double Quote (")' },
25+
{ value: "'", label: "Single Quote (')" },
26+
{ value: '\x00', label: '^0 (\\x00)' },
27+
{ value: '\x01', label: '^A (\\x01)' },
28+
{ value: '\x02', label: '^B (\\x02)' },
29+
{ value: '\x03', label: '^C (\\x03)' }
30+
];
31+
32+
export const sourceConfigs: {
33+
name: keyof FileFormatResponse;
34+
label: string;
35+
options: { label: string; value: string | boolean }[];
36+
}[] = [
37+
{
38+
name: 'fieldSeparator',
39+
label: 'Field Separator',
40+
options: separator
41+
},
42+
{
43+
name: 'recordSeparator',
44+
label: 'Record Separator',
45+
options: separator
46+
},
47+
{
48+
name: 'quoteChar',
49+
label: 'Quote Character',
50+
options: separator
51+
},
52+
{
53+
name: 'hasHeader',
54+
label: 'Has Header',
55+
options: [
56+
{ value: true, label: 'Yes' },
57+
{ value: false, label: 'No' }
58+
]
59+
},
60+
{
61+
name: 'type',
62+
label: 'File Type',
63+
options: [
64+
{ value: 'csv', label: 'CSV File' },
65+
{ value: 'json', label: 'JSON' },
66+
{ value: 'excel', label: 'Excel File' }
67+
]
68+
}
69+
];

0 commit comments

Comments
 (0)