Kaynağa Gözat

[ui-importer] add configure setting feature in file preview (#4101)

Ram Prasad Agarwal 7 ay önce
ebeveyn
işleme
6bb1aad4c0

+ 2 - 3
desktop/core/src/desktop/js/apps/newimporter/ImporterFilePreview/ImporterFilePreview.scss

@@ -21,7 +21,7 @@
     display: flex;
     flex-direction: column;
     gap: 16px;
-    padding: 8px 16px 16px 24px;
+    padding: 8px 24px 24px;
     height: 100%;
 
     &__header {
@@ -57,10 +57,9 @@
 
     &__main-section {
       display: flex;
+      flex-direction: column;
       flex: 1;
-      justify-content: center;
       background-color: white;
-      padding: 16px 0 0 0;
     }
   }
 }

+ 3 - 1
desktop/core/src/desktop/js/apps/newimporter/ImporterFilePreview/ImporterFilePreview.tsx

@@ -27,6 +27,7 @@ import { i18nReact } from '../../../utils/i18nReact';
 import { BorderlessButton, PrimaryButton } from 'cuix/dist/components/Button';
 import PaginatedTable from '../../../reactComponents/PaginatedTable/PaginatedTable';
 import { GUESS_FORMAT_URL, GUESS_FIELD_TYPES_URL } from '../api';
+import SourceConfiguration from './SourceConfiguration/SourceConfiguration';
 
 import './ImporterFilePreview.scss';
 
@@ -36,7 +37,7 @@ interface ImporterFilePreviewProps {
 
 const ImporterFilePreview = ({ fileMetaData }: ImporterFilePreviewProps): JSX.Element => {
   const { t } = i18nReact.useTranslation();
-  const [fileFormat, setFileFormat] = useState<FileFormatResponse | null>(null);
+  const [fileFormat, setFileFormat] = useState<FileFormatResponse | undefined>();
 
   const { save: guessFormat, loading: guessingFormat } = useSaveData<FileFormatResponse>(
     GUESS_FORMAT_URL,
@@ -97,6 +98,7 @@ const ImporterFilePreview = ({ fileMetaData }: ImporterFilePreviewProps): JSX.El
       </div>
       <div className="hue-importer-preview-page__metadata">{t('DESTINATION')}</div>
       <div className="hue-importer-preview-page__main-section">
+        <SourceConfiguration fileFormat={fileFormat} setFileFormat={setFileFormat} />
         <PaginatedTable<ImporterTableData>
           loading={guessingFormat || guessingFields}
           data={tableData}

+ 45 - 0
desktop/core/src/desktop/js/apps/newimporter/ImporterFilePreview/SourceConfiguration/SourceConfiguration.scss

@@ -0,0 +1,45 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+@use 'variables' as vars;
+
+.antd.cuix {
+  .hue-importer-configuration {
+    padding: 16px;
+
+    &__summary {
+      display: flex;
+      gap: 8px;
+      width: max-content;
+      list-style: none;
+      color: vars.$fluidx-blue-600;
+      cursor: pointer;
+    }
+
+    &__dropdown {
+      border: 1px solid vars.$fluidx-gray-600;
+      border-radius: vars.$border-radius-base;
+      width: 100%;
+    }
+  }
+
+  .hue-importer-configuration-options {
+    padding-top: 16px;
+    display: grid;
+    grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
+    grid-gap: 16px;
+  }
+}

+ 48 - 0
desktop/core/src/desktop/js/apps/newimporter/ImporterFilePreview/SourceConfiguration/SourceConfiguration.test.tsx

@@ -0,0 +1,48 @@
+import React from 'react';
+import { render, fireEvent, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import '@testing-library/jest-dom';
+import SourceConfiguration from './SourceConfiguration';
+import { FileFormatResponse } from '../../types';
+import { separator } from '../../constants';
+
+describe('SourceConfiguration Component', () => {
+  const mockSetFileFormat = jest.fn();
+  const mockFileFormat: FileFormatResponse = {
+    quoteChar: '"',
+    recordSeparator: '\\n',
+    type: 'csv',
+    hasHeader: true,
+    fieldSeparator: ',',
+    status: 0
+  };
+
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('should render the component', () => {
+    const { getByText, getAllByRole } = render(
+      <SourceConfiguration fileFormat={mockFileFormat} setFileFormat={mockSetFileFormat} />
+    );
+    expect(getByText('Configure source')).toBeInTheDocument();
+    expect(getAllByRole('combobox')).toHaveLength(5);
+  });
+
+  it('calls setFileFormat on option change', async () => {
+    const { getByText, getAllByRole } = render(
+      <SourceConfiguration fileFormat={mockFileFormat} setFileFormat={mockSetFileFormat} />
+    );
+
+    const selectElement = getAllByRole('combobox')[0];
+    await userEvent.click(selectElement);
+    fireEvent.click(getByText(separator[3].label));
+
+    await waitFor(() =>
+      expect(mockSetFileFormat).toHaveBeenCalledWith({
+        ...mockFileFormat,
+        fieldSeparator: separator[3].value
+      })
+    );
+  });
+});

+ 74 - 0
desktop/core/src/desktop/js/apps/newimporter/ImporterFilePreview/SourceConfiguration/SourceConfiguration.tsx

@@ -0,0 +1,74 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import React, { useCallback } from 'react';
+import Select from 'cuix/dist/components/Select/Select';
+import ConfigureIcon from '@cloudera/cuix-core/icons/react/ConfigureIcon';
+import { i18nReact } from '../../../../utils/i18nReact';
+import { sourceConfigs } from '../../constants';
+import { FileFormatResponse } from '../../types';
+
+import './SourceConfiguration.scss';
+
+interface SourceConfigurationProps {
+  fileFormat?: FileFormatResponse;
+  setFileFormat: (format: FileFormatResponse) => void;
+}
+const SourceConfiguration = ({
+  fileFormat,
+  setFileFormat
+}: SourceConfigurationProps): JSX.Element => {
+  const { t } = i18nReact.useTranslation();
+
+  const onChange = useCallback(
+    (value: string | number | boolean, name: keyof FileFormatResponse) => {
+      if (fileFormat) {
+        setFileFormat({
+          ...fileFormat,
+          [name]: value
+        });
+      }
+    },
+    [fileFormat, setFileFormat]
+  );
+
+  return (
+    <details className="hue-importer-configuration">
+      <summary className="hue-importer-configuration__summary">
+        <ConfigureIcon />
+        {t('Configure source')}
+      </summary>
+      <div className="hue-importer-configuration-options">
+        {sourceConfigs.map(config => (
+          <div key={config.name}>
+            <label htmlFor={config.name}>{t(config.label)}</label>
+            <Select
+              bordered={true}
+              className="hue-importer-configuration__dropdown"
+              id={config.name}
+              options={config.options}
+              onChange={value => onChange(value, config.name)}
+              value={fileFormat?.[config.name]}
+              getPopupContainer={triggerNode => triggerNode.parentElement}
+            />
+          </div>
+        ))}
+      </div>
+    </details>
+  );
+};
+
+export default SourceConfiguration;

+ 69 - 0
desktop/core/src/desktop/js/apps/newimporter/constants.ts

@@ -0,0 +1,69 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { FileFormatResponse } from './types';
+
+export const separator = [
+  { value: ',', label: 'Comma (,)' },
+  { value: '\\t', label: '^Tab (\\t)' },
+  { value: '\\n', label: '^New Line (\\n)' },
+  { value: '|', label: 'Pipe (|)' },
+  { value: '"', label: 'Double Quote (")' },
+  { value: "'", label: "Single Quote (')" },
+  { value: '\x00', label: '^0 (\\x00)' },
+  { value: '\x01', label: '^A (\\x01)' },
+  { value: '\x02', label: '^B (\\x02)' },
+  { value: '\x03', label: '^C (\\x03)' }
+];
+
+export const sourceConfigs: {
+  name: keyof FileFormatResponse;
+  label: string;
+  options: { label: string; value: string | boolean }[];
+}[] = [
+  {
+    name: 'fieldSeparator',
+    label: 'Field Separator',
+    options: separator
+  },
+  {
+    name: 'recordSeparator',
+    label: 'Record Separator',
+    options: separator
+  },
+  {
+    name: 'quoteChar',
+    label: 'Quote Character',
+    options: separator
+  },
+  {
+    name: 'hasHeader',
+    label: 'Has Header',
+    options: [
+      { value: true, label: 'Yes' },
+      { value: false, label: 'No' }
+    ]
+  },
+  {
+    name: 'type',
+    label: 'File Type',
+    options: [
+      { value: 'csv', label: 'CSV File' },
+      { value: 'json', label: 'JSON' },
+      { value: 'excel', label: 'Excel File' }
+    ]
+  }
+];