Browse Source

[ui-storagebrowser] refactor / move actions to separate files for scaling (#3925)

* [ui-storagebrowser] refactor storage browser actions to separate file for scaling

* fixes the typo
Ram Prasad Agarwal 10 months ago
parent
commit
7e9165b181
12 changed files with 977 additions and 230 deletions
  1. 260 0
      desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageBrowserActions/MoveCopy/MoveCopy.test.tsx
  2. 98 0
      desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageBrowserActions/MoveCopy/MoveCopy.tsx
  3. 156 0
      desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageBrowserActions/Rename/Rename.test.tsx
  4. 67 0
      desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageBrowserActions/Rename/Rename.tsx
  5. 156 0
      desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageBrowserActions/Replication/Replication.test.tsx
  6. 67 0
      desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageBrowserActions/Replication/Replication.tsx
  7. 2 2
      desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageBrowserActions/StorageBrowserActions.test.tsx
  8. 61 153
      desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageBrowserActions/StorageBrowserActions.tsx
  9. 77 42
      desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageBrowserActions/StorageBrowserActions.util.ts
  10. 0 0
      desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageBrowserActions/ViewSummary/ViewSummary.scss
  11. 11 16
      desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageBrowserActions/ViewSummary/ViewSummary.test.tsx
  12. 22 17
      desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageBrowserActions/ViewSummary/ViewSummary.tsx

+ 260 - 0
desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageBrowserActions/MoveCopy/MoveCopy.test.tsx

@@ -0,0 +1,260 @@
+// 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
+// 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 from 'react';
+import { render, fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import MoveCopyAction from './MoveCopy';
+import { ActionType } from '../StorageBrowserActions.util';
+import {
+  BULK_COPY_API_URL,
+  BULK_MOVE_API_URL
+} from '../../../../../reactComponents/FileChooser/api';
+import { StorageDirectoryTableData } from '../../../../../reactComponents/FileChooser/types';
+
+const mockFiles: StorageDirectoryTableData[] = [
+  {
+    name: 'folder1',
+    size: '',
+    type: 'dir',
+    permission: 'rwxrwxrwx',
+    mtime: '2021-01-01 00:00:00',
+    path: 'test/path/folder1',
+    user: 'test',
+    group: 'test',
+    replication: 1
+  },
+  {
+    name: 'file1.txt',
+    size: '0 Byte',
+    type: 'file',
+    permission: 'rwxrwxrwx',
+    mtime: '2021-01-01 00:00:00',
+    path: 'test/path/file1.txt',
+    user: 'test',
+    group: 'test',
+    replication: 1
+  }
+];
+jest.mock('../../../../../utils/hooks/useLoadData', () => ({
+  __esModule: true,
+  default: jest.fn(() => ({
+    data: {
+      files: mockFiles
+    },
+    loading: false
+  }))
+}));
+
+const mockSave = jest.fn();
+jest.mock('../../../../../utils/hooks/useSaveData', () => ({
+  __esModule: true,
+  default: jest.fn(() => ({
+    save: mockSave
+  }))
+}));
+
+const currentPath = 'test/path';
+
+describe('MoveCopy Action Component', () => {
+  const mockOnSuccess = jest.fn();
+  const mockOnError = jest.fn();
+  const mockOnClose = jest.fn();
+
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+
+  describe('Copy Actions', () => {
+    it('should render correctly and open the modal', () => {
+      const { getByText } = render(
+        <MoveCopyAction
+          isOpen={true}
+          action={ActionType.Copy}
+          currentPath={currentPath}
+          files={mockFiles}
+          setLoadingFiles={jest.fn()}
+          onSuccess={mockOnSuccess}
+          onError={mockOnError}
+          onClose={mockOnClose}
+        />
+      );
+
+      expect(getByText('Copy to')).toBeInTheDocument();
+      expect(getByText('Copy')).toBeInTheDocument();
+    });
+
+    it('should call handleCopyOrMove with the correct data when the form is submitted', async () => {
+      const newDestPath = 'test/path/folder1';
+
+      const { getByText } = render(
+        <MoveCopyAction
+          isOpen={true}
+          action={ActionType.Copy}
+          currentPath={currentPath}
+          files={mockFiles}
+          setLoadingFiles={jest.fn()}
+          onSuccess={mockOnSuccess}
+          onError={mockOnError}
+          onClose={mockOnClose}
+        />
+      );
+      fireEvent.click(getByText('folder1'));
+
+      const copyButton = getByText('Copy');
+      expect(copyButton).not.toBeDisabled();
+      fireEvent.click(copyButton);
+
+      expect(mockSave).toHaveBeenCalledTimes(1);
+
+      const formData = new FormData();
+      mockFiles.forEach(file => {
+        formData.append('source_path', file.path);
+      });
+      formData.append('destination_path', newDestPath);
+
+      expect(mockSave).toHaveBeenCalledWith(formData, { url: BULK_COPY_API_URL });
+    });
+
+    it('should call onSuccess when the request succeeds', async () => {
+      mockSave.mockImplementationOnce(mockOnSuccess);
+      const { getByText } = render(
+        <MoveCopyAction
+          isOpen={true}
+          action={ActionType.Copy}
+          currentPath={currentPath}
+          files={mockFiles}
+          setLoadingFiles={jest.fn()}
+          onSuccess={mockOnSuccess}
+          onError={mockOnError}
+          onClose={mockOnClose}
+        />
+      );
+
+      fireEvent.click(getByText('folder1'));
+      fireEvent.click(getByText('Copy'));
+
+      expect(mockSave).toHaveBeenCalledTimes(1);
+      expect(mockOnSuccess).toHaveBeenCalledTimes(1);
+    });
+
+    it('should call onError when the request fails', async () => {
+      mockSave.mockImplementationOnce(() => {
+        mockOnError(new Error());
+      });
+      const { getByText } = render(
+        <MoveCopyAction
+          isOpen={true}
+          action={ActionType.Copy}
+          currentPath={currentPath}
+          files={mockFiles}
+          setLoadingFiles={jest.fn()}
+          onSuccess={mockOnSuccess}
+          onError={mockOnError}
+          onClose={mockOnClose}
+        />
+      );
+
+      fireEvent.click(getByText('folder1'));
+      fireEvent.click(getByText('Copy'));
+
+      expect(mockSave).toHaveBeenCalledTimes(1);
+      expect(mockOnError).toHaveBeenCalledTimes(1);
+    });
+
+    it('should call onClose when the modal is closed', () => {
+      const { getByText } = render(
+        <MoveCopyAction
+          isOpen={true}
+          action={ActionType.Copy}
+          currentPath={currentPath}
+          files={mockFiles}
+          setLoadingFiles={jest.fn()}
+          onSuccess={mockOnSuccess}
+          onError={mockOnError}
+          onClose={mockOnClose}
+        />
+      );
+
+      fireEvent.click(getByText('Cancel'));
+
+      expect(mockOnClose).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('Move Actions', () => {
+    it('should render correctly and open the modal', () => {
+      const { getByText } = render(
+        <MoveCopyAction
+          isOpen={true}
+          action={ActionType.Move}
+          currentPath={currentPath}
+          files={mockFiles}
+          setLoadingFiles={jest.fn()}
+          onSuccess={mockOnSuccess}
+          onError={mockOnError}
+          onClose={mockOnClose}
+        />
+      );
+
+      expect(getByText('Move to')).toBeInTheDocument();
+      expect(getByText('Move')).toBeInTheDocument();
+    });
+
+    it('should call handleCopyOrMove with the correct data when the form is submitted', async () => {
+      const newDestPath = 'test/path/folder1';
+
+      const { getByText } = render(
+        <MoveCopyAction
+          isOpen={true}
+          action={ActionType.Move}
+          currentPath={currentPath}
+          files={mockFiles}
+          setLoadingFiles={jest.fn()}
+          onSuccess={mockOnSuccess}
+          onError={mockOnError}
+          onClose={mockOnClose}
+        />
+      );
+      fireEvent.click(getByText('folder1'));
+
+      const moveButton = getByText('Move');
+      expect(moveButton).not.toBeDisabled();
+      fireEvent.click(moveButton);
+
+      expect(mockSave).toHaveBeenCalledTimes(1);
+
+      const formData = new FormData();
+      mockFiles.forEach(file => {
+        formData.append('source_path', file.path);
+      });
+      formData.append('destination_path', newDestPath);
+
+      expect(mockSave).toHaveBeenCalledWith(formData, { url: BULK_MOVE_API_URL });
+    });
+  });
+});

+ 98 - 0
desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageBrowserActions/MoveCopy/MoveCopy.tsx

@@ -0,0 +1,98 @@
+// 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 from 'react';
+import { i18nReact } from '../../../../../utils/i18nReact';
+import useSaveData from '../../../../../utils/hooks/useSaveData';
+import { ActionType } from '../StorageBrowserActions.util';
+import {
+  BULK_COPY_API_URL,
+  BULK_MOVE_API_URL
+} from '../../../../../reactComponents/FileChooser/api';
+import FileChooserModal from '../../../FileChooserModal/FileChooserModal';
+import {
+  FileStats,
+  StorageDirectoryTableData
+} from '../../../../../reactComponents/FileChooser/types';
+
+interface MoveCopyActionProps {
+  isOpen?: boolean;
+  action: ActionType.Copy | ActionType.Move;
+  currentPath: FileStats['path'];
+  files: StorageDirectoryTableData[];
+  setLoadingFiles: (value: boolean) => void;
+  onSuccess: () => void;
+  onError: (error: Error) => void;
+  onClose: () => void;
+}
+
+const MoveCopyAction = ({
+  isOpen = true,
+  action,
+  currentPath,
+  files,
+  setLoadingFiles,
+  onSuccess,
+  onError,
+  onClose
+}: MoveCopyActionProps): JSX.Element => {
+  const { t } = i18nReact.useTranslation();
+
+  const { save: saveForm } = useSaveData(undefined, {
+    postOptions: {
+      qsEncodeData: false,
+      headers: {
+        'Content-Type': 'multipart/form-data'
+      }
+    },
+    skip: !files.length,
+    onSuccess: onSuccess,
+    onError: onError
+  });
+
+  const handleCopyOrMove = (destination_path: string) => {
+    const url = {
+      [ActionType.Copy]: BULK_COPY_API_URL,
+      [ActionType.Move]: BULK_MOVE_API_URL
+    }[action];
+
+    if (!url) {
+      return;
+    }
+
+    const formData = new FormData();
+    files.forEach(selectedFile => {
+      formData.append('source_path', selectedFile.path);
+    });
+    formData.append('destination_path', destination_path);
+
+    setLoadingFiles(true);
+    saveForm(formData, { url });
+  };
+
+  return (
+    <FileChooserModal
+      onClose={onClose}
+      onSubmit={handleCopyOrMove}
+      showModal={isOpen}
+      title={action === ActionType.Move ? t('Move to') : t('Copy to')}
+      sourcePath={currentPath}
+      submitText={action === ActionType.Move ? t('Move') : t('Copy')}
+    />
+  );
+};
+
+export default MoveCopyAction;

+ 156 - 0
desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageBrowserActions/Rename/Rename.test.tsx

@@ -0,0 +1,156 @@
+// 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 from 'react';
+import { render, fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import RenameAction from './Rename';
+import { StorageDirectoryTableData } from '../../../../../reactComponents/FileChooser/types';
+import { RENAME_API_URL } from '../../../../../reactComponents/FileChooser/api';
+
+const mockSave = jest.fn();
+jest.mock('../../../../../utils/hooks/useSaveData', () => ({
+  __esModule: true,
+  default: jest.fn(() => ({
+    save: mockSave,
+    loading: false
+  }))
+}));
+
+describe('RenameAction Component', () => {
+  const mockOnSuccess = jest.fn();
+  const mockOnError = jest.fn();
+  const mockOnClose = jest.fn();
+
+  const file: StorageDirectoryTableData = {
+    name: 'file1.txt',
+    size: '0 Byte',
+    type: 'file',
+    permission: 'rwxrwxrwx',
+    mtime: '2021-01-01 00:00:00',
+    path: '/path/to/file1.txt',
+    user: 'test',
+    group: 'test',
+    replication: 1
+  };
+
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('should render the Rename modal with the correct title and initial input', () => {
+    const { getByText, getByRole } = render(
+      <RenameAction
+        isOpen={true}
+        file={file}
+        onSuccess={mockOnSuccess}
+        onError={mockOnError}
+        onClose={mockOnClose}
+      />
+    );
+
+    expect(getByText('Rename', { selector: 'div' })).toBeInTheDocument();
+
+    const input = getByRole('textbox');
+    expect(input).toBeInTheDocument();
+    expect(input).toHaveValue('file1.txt');
+  });
+
+  it('should call handleRename with the correct data when the form is submitted', async () => {
+    const { getByRole } = render(
+      <RenameAction
+        isOpen={true}
+        file={file}
+        onSuccess={mockOnSuccess}
+        onError={mockOnError}
+        onClose={mockOnClose}
+      />
+    );
+
+    const input = getByRole('textbox');
+    fireEvent.change(input, { target: { value: 'file2.txt' } });
+
+    const renameButton = getByRole('button', { name: 'Rename' });
+    fireEvent.click(renameButton);
+
+    expect(mockSave).toHaveBeenCalledTimes(1);
+
+    expect(mockSave).toHaveBeenCalledWith(
+      { source_path: '/path/to/file1.txt', destination_path: 'file2.txt' },
+      { url: RENAME_API_URL }
+    );
+  });
+
+  it('should call onSuccess when the rename request succeeds', async () => {
+    mockSave.mockImplementationOnce(mockOnSuccess);
+    const { getByRole } = render(
+      <RenameAction
+        isOpen={true}
+        file={file}
+        onSuccess={mockOnSuccess}
+        onError={mockOnError}
+        onClose={mockOnClose}
+      />
+    );
+
+    const input = getByRole('textbox');
+    fireEvent.change(input, { target: { value: 'file2.txt' } });
+
+    const renameButton = getByRole('button', { name: 'Rename' });
+    fireEvent.click(renameButton);
+
+    expect(mockOnSuccess).toHaveBeenCalledTimes(1);
+  });
+
+  it('should call onError when the rename request fails', async () => {
+    mockSave.mockImplementationOnce(() => {
+      mockOnError(new Error());
+    });
+    const { getByRole } = render(
+      <RenameAction
+        isOpen={true}
+        file={file}
+        onSuccess={mockOnSuccess}
+        onError={mockOnError}
+        onClose={mockOnClose}
+      />
+    );
+
+    const input = getByRole('textbox');
+    fireEvent.change(input, { target: { value: 'file2.txt' } });
+
+    const renameButton = getByRole('button', { name: 'Rename' });
+    fireEvent.click(renameButton);
+
+    expect(mockOnError).toHaveBeenCalledTimes(1);
+  });
+
+  it('should call onClose when the modal is closed', () => {
+    const { getByRole } = render(
+      <RenameAction
+        isOpen={true}
+        file={file}
+        onSuccess={mockOnSuccess}
+        onError={mockOnError}
+        onClose={mockOnClose}
+      />
+    );
+
+    fireEvent.click(getByRole('button', { name: 'Cancel' }));
+
+    expect(mockOnClose).toHaveBeenCalledTimes(1);
+  });
+});

+ 67 - 0
desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageBrowserActions/Rename/Rename.tsx

@@ -0,0 +1,67 @@
+// 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 from 'react';
+import { StorageDirectoryTableData } from '../../../../../reactComponents/FileChooser/types';
+import { i18nReact } from '../../../../../utils/i18nReact';
+import useSaveData from '../../../../../utils/hooks/useSaveData';
+import { RENAME_API_URL } from '../../../../../reactComponents/FileChooser/api';
+import InputModal from '../../../InputModal/InputModal';
+
+interface RenameActionProps {
+  isOpen?: boolean;
+  file: StorageDirectoryTableData;
+  onSuccess: () => void;
+  onError: (error: Error) => void;
+  onClose: () => void;
+}
+
+const RenameAction = ({
+  isOpen = true,
+  file,
+  onSuccess,
+  onError,
+  onClose
+}: RenameActionProps): JSX.Element => {
+  const { t } = i18nReact.useTranslation();
+
+  const { save, loading } = useSaveData(undefined, {
+    skip: !file.path,
+    onSuccess,
+    onError
+  });
+
+  const handleRename = (value: string) => {
+    const payload = { source_path: file.path, destination_path: value };
+    save(payload, { url: RENAME_API_URL });
+  };
+
+  return (
+    <InputModal
+      title={t('Rename')}
+      inputLabel={t('Enter new name')}
+      submitText={t('Rename')}
+      showModal={isOpen}
+      onSubmit={handleRename}
+      onClose={onClose}
+      inputType="text"
+      initialValue={file.name}
+      buttonDisabled={loading}
+    />
+  );
+};
+
+export default RenameAction;

+ 156 - 0
desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageBrowserActions/Replication/Replication.test.tsx

@@ -0,0 +1,156 @@
+// 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 from 'react';
+import { render, fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import ReplicationAction from './Replication';
+import { StorageDirectoryTableData } from '../../../../../reactComponents/FileChooser/types';
+import { SET_REPLICATION_API_URL } from '../../../../../reactComponents/FileChooser/api';
+
+const mockSave = jest.fn();
+jest.mock('../../../../../utils/hooks/useSaveData', () => ({
+  __esModule: true,
+  default: jest.fn(() => ({
+    save: mockSave,
+    loading: false
+  }))
+}));
+
+describe('ReplicationAction Component', () => {
+  const mockOnSuccess = jest.fn();
+  const mockOnError = jest.fn();
+  const mockOnClose = jest.fn();
+
+  const file: StorageDirectoryTableData = {
+    name: 'file1.txt',
+    size: '0 Byte',
+    type: 'file',
+    permission: 'rwxrwxrwx',
+    mtime: '2021-01-01 00:00:00',
+    path: '/path/to/file1.txt',
+    user: 'test',
+    group: 'test',
+    replication: 1
+  };
+
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('should render the Replication modal with the correct title and initial input', () => {
+    const { getByText, getByRole } = render(
+      <ReplicationAction
+        isOpen={true}
+        file={file}
+        onSuccess={mockOnSuccess}
+        onError={mockOnError}
+        onClose={mockOnClose}
+      />
+    );
+
+    expect(getByText('Setting Replication factor for: /path/to/file1.txt')).toBeInTheDocument();
+
+    const input = getByRole('spinbutton');
+    expect(input).toBeInTheDocument();
+    expect(input).toHaveValue(1);
+  });
+
+  it('should call handleReplication with the correct data when the form is submitted', async () => {
+    const { getByRole } = render(
+      <ReplicationAction
+        isOpen={true}
+        file={file}
+        onSuccess={mockOnSuccess}
+        onError={mockOnError}
+        onClose={mockOnClose}
+      />
+    );
+
+    const input = getByRole('spinbutton');
+    fireEvent.change(input, { target: { value: 2 } });
+
+    const replicationButton = getByRole('button', { name: 'Submit' });
+    fireEvent.click(replicationButton);
+
+    expect(mockSave).toHaveBeenCalledTimes(1);
+
+    expect(mockSave).toHaveBeenCalledWith(
+      { path: '/path/to/file1.txt', replication_factor: '2' },
+      { url: SET_REPLICATION_API_URL }
+    );
+  });
+
+  it('should call onSuccess when the rename request succeeds', async () => {
+    mockSave.mockImplementationOnce(mockOnSuccess);
+    const { getByRole } = render(
+      <ReplicationAction
+        isOpen={true}
+        file={file}
+        onSuccess={mockOnSuccess}
+        onError={mockOnError}
+        onClose={mockOnClose}
+      />
+    );
+
+    const input = getByRole('spinbutton');
+    fireEvent.change(input, { target: { value: 2 } });
+
+    const replicationButton = getByRole('button', { name: 'Submit' });
+    fireEvent.click(replicationButton);
+
+    expect(mockOnSuccess).toHaveBeenCalledTimes(1);
+  });
+
+  it('should call onError when the rename request fails', async () => {
+    mockSave.mockImplementationOnce(() => {
+      mockOnError(new Error());
+    });
+    const { getByRole } = render(
+      <ReplicationAction
+        isOpen={true}
+        file={file}
+        onSuccess={mockOnSuccess}
+        onError={mockOnError}
+        onClose={mockOnClose}
+      />
+    );
+
+    const input = getByRole('spinbutton');
+    fireEvent.change(input, { target: { value: 2 } });
+
+    const replicationButton = getByRole('button', { name: 'Submit' });
+    fireEvent.click(replicationButton);
+
+    expect(mockOnError).toHaveBeenCalledTimes(1);
+  });
+
+  it('should call onClose when the modal is closed', () => {
+    const { getByRole } = render(
+      <ReplicationAction
+        isOpen={true}
+        file={file}
+        onSuccess={mockOnSuccess}
+        onError={mockOnError}
+        onClose={mockOnClose}
+      />
+    );
+
+    fireEvent.click(getByRole('button', { name: 'Cancel' }));
+
+    expect(mockOnClose).toHaveBeenCalledTimes(1);
+  });
+});

+ 67 - 0
desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageBrowserActions/Replication/Replication.tsx

@@ -0,0 +1,67 @@
+// 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 from 'react';
+import { StorageDirectoryTableData } from '../../../../../reactComponents/FileChooser/types';
+import { i18nReact } from '../../../../../utils/i18nReact';
+import useSaveData from '../../../../../utils/hooks/useSaveData';
+import { SET_REPLICATION_API_URL } from '../../../../../reactComponents/FileChooser/api';
+import InputModal from '../../../InputModal/InputModal';
+
+interface ReplicationActionProps {
+  isOpen?: boolean;
+  file: StorageDirectoryTableData;
+  onSuccess: () => void;
+  onError: (error: Error) => void;
+  onClose: () => void;
+}
+
+const ReplicationAction = ({
+  isOpen = true,
+  file,
+  onSuccess,
+  onError,
+  onClose
+}: ReplicationActionProps): JSX.Element => {
+  const { t } = i18nReact.useTranslation();
+
+  const { save, loading } = useSaveData(undefined, {
+    skip: !file.path,
+    onSuccess,
+    onError
+  });
+
+  const handleReplication = (replicationFactor: number) => {
+    const payload = { path: file.path, replication_factor: replicationFactor };
+    save(payload, { url: SET_REPLICATION_API_URL });
+  };
+
+  return (
+    <InputModal
+      title={t('Setting Replication factor for: ') + file.path}
+      inputLabel={t('Replication factor:')}
+      submitText={t('Submit')}
+      showModal={isOpen}
+      onSubmit={handleReplication}
+      onClose={onClose}
+      inputType="number"
+      initialValue={file.replication}
+      buttonDisabled={loading}
+    />
+  );
+};
+
+export default ReplicationAction;

+ 2 - 2
desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageBrowserActions/StorageBrowserActions.test.tsx

@@ -193,7 +193,7 @@ describe('StorageBrowserRowActions', () => {
     test('renders rename modal when rename option is clicked', async () => {
       const user = userEvent.setup();
       await setUpActionMenu([mockRecord], 'abfs://test', 'dir');
-      await user.click(screen.queryByRole('menuitem', { name: 'Rename' }));
+      await user.click(screen.getByRole('menuitem', { name: 'Rename' }));
       expect(await screen.findByText('Enter new name')).toBeInTheDocument();
     });
   });
@@ -232,7 +232,7 @@ describe('StorageBrowserRowActions', () => {
     test('renders set replication modal when set replication option is clicked', async () => {
       const user = userEvent.setup();
       await setUpActionMenu([mockRecord], 'hdfs://test', 'file');
-      await user.click(screen.queryByRole('menuitem', { name: 'Set Replication' }));
+      await user.click(screen.getByRole('menuitem', { name: 'Set Replication' }));
       expect(await screen.findByText(/Setting Replication factor/i)).toBeInTheDocument();
     });
   });

+ 61 - 153
desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageBrowserActions/StorageBrowserActions.tsx

@@ -21,30 +21,24 @@ import { MenuItemType } from 'antd/lib/menu/hooks/useItems';
 import Button from 'cuix/dist/components/Button';
 import DropDownIcon from '@cloudera/cuix-core/icons/react/DropdownIcon';
 import InfoIcon from '@cloudera/cuix-core/icons/react/InfoIcon';
+import EditIcon from '@cloudera/cuix-core/icons/react/EditIcon';
 import DuplicateIcon from '@cloudera/cuix-core/icons/react/DuplicateIcon';
 import CopyClipboardIcon from '@cloudera/cuix-core/icons/react/CopyClipboardIcon';
+import DataMovementIcon from '@cloudera/cuix-core/icons/react/DataMovementIcon';
+import DeleteIcon from '@cloudera/cuix-core/icons/react/DeleteIcon';
 
 import { i18nReact } from '../../../../utils/i18nReact';
-import { inTrash } from '../../../../utils/storageBrowserUtils';
-import {
-  RENAME_API_URL,
-  SET_REPLICATION_API_URL,
-  BULK_COPY_API_URL,
-  BULK_MOVE_API_URL
-} from '../../../../reactComponents/FileChooser/api';
 import huePubSub from '../../../../utils/huePubSub';
-import useSaveData from '../../../../utils/hooks/useSaveData';
-
-import SummaryModal from '../SummaryModal/SummaryModal';
-import InputModal from '../../InputModal/InputModal';
-import FileChooserModal from '../../FileChooserModal/FileChooserModal';
-
 import './StorageBrowserActions.scss';
 import {
   FileStats,
   StorageDirectoryTableData
 } from '../../../../reactComponents/FileChooser/types';
-import { ActionType, getActionsConfig } from './StorageBrowserActions.util';
+import { ActionType, getEnabledActions } from './StorageBrowserActions.util';
+import MoveCopyAction from './MoveCopy/MoveCopy';
+import RenameAction from './Rename/Rename';
+import ReplicationAction from './Replication/Replication';
+import ViewSummary from './ViewSummary/ViewSummary';
 
 interface StorageBrowserRowActionsProps {
   currentPath: FileStats['path'];
@@ -53,19 +47,32 @@ interface StorageBrowserRowActionsProps {
   setLoadingFiles: (value: boolean) => void;
 }
 
+const iconsMap: Record<ActionType, JSX.Element> = {
+  [ActionType.Copy]: <CopyClipboardIcon />,
+  [ActionType.Move]: <DataMovementIcon />,
+  [ActionType.Rename]: <EditIcon />,
+  [ActionType.Replication]: <DuplicateIcon />,
+  [ActionType.Delete]: <DeleteIcon />,
+  [ActionType.Summary]: <InfoIcon />
+};
+
 const StorageBrowserActions = ({
   currentPath,
   selectedFiles,
   onSuccessfulAction,
   setLoadingFiles
 }: StorageBrowserRowActionsProps): JSX.Element => {
-  const [selectedFilePath, setSelectedFilePath] = useState<string>('');
   const [selectedAction, setSelectedAction] = useState<ActionType>();
 
   const { t } = i18nReact.useTranslation();
 
+  const closeModal = () => {
+    setSelectedAction(undefined);
+  };
+
   const onApiSuccess = () => {
     setLoadingFiles(false);
+    closeModal();
     onSuccessfulAction();
   };
 
@@ -74,111 +81,15 @@ const StorageBrowserActions = ({
     huePubSub.publish('hue.error', error);
   };
 
-  const { save } = useSaveData(undefined, {
-    onSuccess: onApiSuccess,
-    onError: onApiError
-  });
-
-  const { save: saveForm } = useSaveData(undefined, {
-    postOptions: {
-      qsEncodeData: false,
-      headers: {
-        'Content-Type': 'multipart/form-data'
-      }
-    },
-    onSuccess: onApiSuccess,
-    onError: onApiError
-  });
-
-  const handleRename = (value: string) => {
-    setLoadingFiles(true);
-    const payload = { source_path: selectedFilePath, destination_path: value };
-    save(payload, { url: RENAME_API_URL });
-  };
-
-  const handleReplication = (replicationFactor: number) => {
-    const payload = { path: selectedFilePath, replication_factor: replicationFactor };
-    save(payload, { url: SET_REPLICATION_API_URL });
-  };
-
-  const handleCopyOrMove = (destination_path: string) => {
-    const url = {
-      [ActionType.Copy]: BULK_COPY_API_URL,
-      [ActionType.Move]: BULK_MOVE_API_URL
-    }[selectedAction ?? ''];
-
-    if (!url) {
-      return;
-    }
-
-    const formData = new FormData();
-    selectedFiles.map(selectedFile => {
-      formData.append('source_path', selectedFile.path);
-    });
-    formData.append('destination_path', destination_path);
-
-    setLoadingFiles(true);
-    saveForm(formData, { url });
-  };
-
-  const actionConfig = getActionsConfig(selectedFiles);
-
-  const onActionClick = (action: ActionType, path: string) => () => {
-    setSelectedFilePath(path);
-    setSelectedAction(action);
-  };
-
-  const onModalClose = () => {
-    setSelectedFilePath('');
-    setSelectedAction(undefined);
-  };
-
   const actionItems: MenuItemType[] = useMemo(() => {
-    const isActionEnabled =
-      selectedFiles && selectedFiles.length > 0 && !inTrash(selectedFiles[0].path);
-    if (!isActionEnabled) {
-      return [];
-    }
-
-    const actions: MenuItemType[] = [
-      {
-        disabled: !actionConfig.isCopyEnabled,
-        key: ActionType.Copy,
-        icon: <CopyClipboardIcon />,
-        label: t('Copy'),
-        onClick: onActionClick(ActionType.Copy, selectedFiles[0].path)
-      },
-      {
-        disabled: !actionConfig.isMoveEnabled,
-        key: ActionType.Move,
-        icon: <CopyClipboardIcon />,
-        label: t('Move'),
-        onClick: onActionClick(ActionType.Move, selectedFiles[0].path)
-      },
-      {
-        disabled: !actionConfig.isSummaryEnabled,
-        key: ActionType.Summary,
-        icon: <InfoIcon />,
-        label: t('View Summary'),
-        onClick: onActionClick(ActionType.Summary, selectedFiles[0].path)
-      },
-      {
-        disabled: !actionConfig.isRenameEnabled,
-        key: ActionType.Rename,
-        icon: <InfoIcon />,
-        label: t('Rename'),
-        onClick: onActionClick(ActionType.Rename, selectedFiles[0].path)
-      },
-      {
-        disabled: !actionConfig.isReplicationEnabled,
-        key: ActionType.Repilcation,
-        icon: <DuplicateIcon />,
-        label: t('Set Replication'),
-        onClick: onActionClick(ActionType.Repilcation, selectedFiles[0].path)
-      }
-    ].filter(e => !e.disabled);
-    return actions;
-  }, [selectedFiles, actionConfig]);
+    const enabledActions = getEnabledActions(selectedFiles);
+    return enabledActions.map(action => ({
+      key: String(action.type),
+      label: t(action.label),
+      icon: iconsMap[action.type],
+      onClick: () => setSelectedAction(action.type)
+    }));
+  }, [selectedFiles]);
 
   return (
     <>
@@ -189,46 +100,43 @@ const StorageBrowserActions = ({
           className: 'hue-storage-browser__table-actions-menu'
         }}
         trigger={['click']}
-        disabled={actionItems.length === 0 ? true : false}
+        disabled={!actionItems.length}
       >
         <Button data-event="">
           {t('Actions')}
           <DropDownIcon />
         </Button>
       </Dropdown>
-      <SummaryModal
-        showModal={selectedAction === ActionType.Summary}
-        path={selectedFilePath}
-        onClose={onModalClose}
-      />
-      <InputModal
-        title={t('Rename')}
-        inputLabel={t('Enter new name')}
-        submitText={t('Rename')}
-        showModal={selectedAction === ActionType.Rename}
-        onSubmit={handleRename}
-        onClose={onModalClose}
-        inputType="text"
-        initialValue={selectedFiles[0]?.name}
-      />
-      <InputModal
-        title={t('Setting Replication factor for: ') + selectedFilePath}
-        inputLabel={t('Replication factor:')}
-        submitText={t('Submit')}
-        showModal={selectedAction === ActionType.Repilcation}
-        onSubmit={handleReplication}
-        onClose={onModalClose}
-        inputType="number"
-        initialValue={selectedFiles[0]?.replication}
-      />
-      <FileChooserModal
-        onClose={onModalClose}
-        onSubmit={handleCopyOrMove}
-        showModal={selectedAction === ActionType.Move || selectedAction === ActionType.Copy}
-        title={selectedAction === ActionType.Move ? t('Move to') : t('Copy to')}
-        sourcePath={currentPath}
-        submitText={selectedAction === ActionType.Move ? t('Move') : t('Copy')}
-      />
+      {selectedAction === ActionType.Summary && (
+        <ViewSummary path={selectedFiles[0].path} onClose={closeModal} />
+      )}
+      {selectedAction === ActionType.Rename && (
+        <RenameAction
+          file={selectedFiles[0]}
+          onSuccess={onApiSuccess}
+          onError={onApiError}
+          onClose={closeModal}
+        />
+      )}
+      {selectedAction === ActionType.Replication && (
+        <ReplicationAction
+          file={selectedFiles[0]}
+          onSuccess={onApiSuccess}
+          onError={onApiError}
+          onClose={closeModal}
+        />
+      )}
+      {(selectedAction === ActionType.Move || selectedAction === ActionType.Copy) && (
+        <MoveCopyAction
+          action={selectedAction}
+          files={selectedFiles}
+          currentPath={currentPath}
+          onSuccess={onApiSuccess}
+          onError={onApiError}
+          onClose={closeModal}
+          setLoadingFiles={setLoadingFiles}
+        />
+      )}
     </>
   );
 };

+ 77 - 42
desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageBrowserActions/StorageBrowserActions.util.ts

@@ -1,4 +1,7 @@
-import { StorageDirectoryTableData } from '../../../../reactComponents/FileChooser/types';
+import {
+  BrowserViewType,
+  StorageDirectoryTableData
+} from '../../../../reactComponents/FileChooser/types';
 import {
   isHDFS,
   isOFS,
@@ -10,7 +13,8 @@ import {
   isABFS,
   isGS,
   isS3,
-  isOFSRoot
+  isOFSRoot,
+  inTrash
 } from '../../../../utils/storageBrowserUtils';
 
 export enum ActionType {
@@ -18,10 +22,11 @@ export enum ActionType {
   Move = 'move',
   Summary = 'summary',
   Rename = 'rename',
-  Repilcation = 'repilcation'
+  Replication = 'replication',
+  Delete = 'delete'
 }
 
-const isValidFileOrFolder = (filePath: string) => {
+const isValidFileOrFolder = (filePath: string): boolean => {
   return (
     isHDFS(filePath) ||
     (isS3(filePath) && !isS3Root(filePath)) ||
@@ -31,52 +36,82 @@ const isValidFileOrFolder = (filePath: string) => {
   );
 };
 
-const isSummaryEnabled = (files: StorageDirectoryTableData[]) => {
-  if (files.length !== 1) {
-    return false;
+const isActionEnabled = (file: StorageDirectoryTableData, action: ActionType): boolean => {
+  switch (action) {
+    case ActionType.Summary:
+      return (isHDFS(file.path) || isOFS(file.path)) && file.type === BrowserViewType.file;
+    case ActionType.Replication:
+      return isHDFS(file.path) && file.type === BrowserViewType.file;
+    case ActionType.Rename:
+    case ActionType.Copy:
+    case ActionType.Delete:
+    case ActionType.Move:
+      return isValidFileOrFolder(file.path);
+    default:
+      return false;
   }
-  const selectedFile = files[0];
-  return (isHDFS(selectedFile.path) || isOFS(selectedFile.path)) && selectedFile.type === 'file';
 };
 
-const isRenameEnabled = (files: StorageDirectoryTableData[]) => {
-  if (files.length !== 1) {
-    return false;
-  }
-  const filePath = files[0].path;
-  return isValidFileOrFolder(filePath);
+const isSingleFileActionEnabled = (
+  files: StorageDirectoryTableData[],
+  action: ActionType
+): boolean => {
+  return files.length === 1 && isActionEnabled(files[0], action);
 };
 
-const isReplicationEnabled = (files: StorageDirectoryTableData[]) => {
-  if (files.length !== 1) {
-    return false;
-  }
-  const selectedFile = files[0];
-  return isHDFS(selectedFile.path) && selectedFile.type === 'file';
+const isMultipleFileActionEnabled = (
+  files: StorageDirectoryTableData[],
+  action: ActionType
+): boolean => {
+  return files.length !== 0 && files.every(file => isActionEnabled(file, action));
 };
 
-const isCopyEnabled = (files: StorageDirectoryTableData[]) => {
-  if (files.length > 0) {
-    const filePath = files[0].path;
-    return isValidFileOrFolder(filePath);
+export const getEnabledActions = (
+  files: StorageDirectoryTableData[]
+): {
+  enabled: boolean;
+  type: ActionType;
+  label: string;
+}[] => {
+  const isAnyFileInTrash = files.some(file => inTrash(file.path));
+  const isNoFileSelected = files && files.length === 0;
+  if (isAnyFileInTrash || isNoFileSelected) {
+    return [];
   }
-  return false;
-};
 
-const isMoveEnabled = (files: StorageDirectoryTableData[]) => {
-  if (files.length > 0) {
-    const filePath = files[0].path;
-    return isValidFileOrFolder(filePath);
-  }
-  return false;
-};
+  // order of the elements will be the order of the action menu
+  const actions = [
+    {
+      enabled: isMultipleFileActionEnabled(files, ActionType.Copy),
+      type: ActionType.Copy,
+      label: 'Copy'
+    },
+    {
+      enabled: isMultipleFileActionEnabled(files, ActionType.Move),
+      type: ActionType.Move,
+      label: 'Move'
+    },
+    {
+      enabled: isSingleFileActionEnabled(files, ActionType.Summary),
+      type: ActionType.Summary,
+      label: 'View Summary'
+    },
+    {
+      enabled: isSingleFileActionEnabled(files, ActionType.Rename),
+      type: ActionType.Rename,
+      label: 'Rename'
+    },
+    {
+      enabled: isMultipleFileActionEnabled(files, ActionType.Delete),
+      type: ActionType.Delete,
+      label: 'Delete'
+    },
+    {
+      enabled: isSingleFileActionEnabled(files, ActionType.Replication),
+      type: ActionType.Replication,
+      label: 'Set Replication'
+    }
+  ].filter(e => e.enabled);
 
-export const getActionsConfig = (files: StorageDirectoryTableData[]): Record<string, boolean> => {
-  return {
-    isSummaryEnabled: isSummaryEnabled(files),
-    isRenameEnabled: isRenameEnabled(files),
-    isReplicationEnabled: isReplicationEnabled(files),
-    isCopyEnabled: isCopyEnabled(files),
-    isMoveEnabled: isMoveEnabled(files)
-  };
+  return actions;
 };

+ 0 - 0
desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/SummaryModal/SummaryModal.scss → desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageBrowserActions/ViewSummary/ViewSummary.scss


+ 11 - 16
desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/SummaryModal/SummaryModal.test.tsx → desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageBrowserActions/ViewSummary/ViewSummary.test.tsx

@@ -13,22 +13,22 @@
 // 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 from 'react';
 import { waitFor, screen, fireEvent, render } from '@testing-library/react';
 import '@testing-library/jest-dom';
 
-import { get } from '../../../../api/utils';
-import formatBytes from '../../../../utils/formatBytes';
-import SummaryModal from './SummaryModal';
+import { get } from '../../../../../api/utils';
+import formatBytes from '../../../../../utils/formatBytes';
+import ViewSummary from './ViewSummary';
 
-// Mock the `get` function
-jest.mock('../../../../api/utils', () => ({
+jest.mock('../../../../../api/utils', () => ({
   get: jest.fn()
 }));
 
 const mockGet = get as jest.MockedFunction<typeof get>;
 
-describe('SummaryModal', () => {
+describe('ViewSummary', () => {
   beforeAll(() => {
     jest.clearAllMocks();
   });
@@ -52,19 +52,16 @@ describe('SummaryModal', () => {
     typeQuota: -1,
     replication: 3
   };
+
   it('should render path of file in title', async () => {
-    const { getByText } = render(
-      <SummaryModal showModal={true} onClose={() => {}} path="some/path" />
-    );
+    const { getByText } = render(<ViewSummary onClose={() => {}} path="some/path" />);
     await waitFor(async () => {
       expect(getByText('Summary for some/path')).toBeInTheDocument();
     });
   });
 
   it('should render summary content after successful data fetching', async () => {
-    const { getByText, getAllByText } = render(
-      <SummaryModal showModal={true} onClose={() => {}} path="some/path" />
-    );
+    const { getByText, getAllByText } = render(<ViewSummary onClose={() => {}} path="some/path" />);
     await waitFor(async () => {
       expect(getByText('Diskspace Consumed')).toBeInTheDocument();
       expect(getAllByText(formatBytes(mockSummary.spaceConsumed))[0]).toBeInTheDocument();
@@ -72,7 +69,7 @@ describe('SummaryModal', () => {
   });
 
   it('should render space consumed in Bytes after the values are formatted', async () => {
-    render(<SummaryModal path={'/user/demo'} showModal={true} onClose={() => {}} />);
+    render(<ViewSummary path={'/user/demo'} onClose={() => {}} />);
     const spaceConsumed = await screen.findAllByText('0 Byte');
     await waitFor(() => {
       expect(spaceConsumed[0]).toBeInTheDocument();
@@ -81,9 +78,7 @@ describe('SummaryModal', () => {
 
   it('should call onClose function when close button is clicked', async () => {
     const mockOnClose = jest.fn();
-    const { getByText } = render(
-      <SummaryModal showModal={true} onClose={mockOnClose} path="some/path" />
-    );
+    const { getByText } = render(<ViewSummary onClose={mockOnClose} path="some/path" />);
 
     const closeButton = getByText('Close');
     expect(mockOnClose).not.toHaveBeenCalled();

+ 22 - 17
desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/SummaryModal/SummaryModal.tsx → desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageBrowserActions/ViewSummary/ViewSummary.tsx

@@ -18,30 +18,33 @@ import React from 'react';
 import Modal from 'cuix/dist/components/Modal';
 import { Spin } from 'antd';
 
-import huePubSub from '../../../../utils/huePubSub';
-import { i18nReact } from '../../../../utils/i18nReact';
-import formatBytes from '../../../../utils/formatBytes';
-import useLoadData from '../../../../utils/hooks/useLoadData';
-import { CONTENT_SUMMARY_API_URL } from '../../../../reactComponents/FileChooser/api';
-import { ContentSummary } from '../../../../reactComponents/FileChooser/types';
+import huePubSub from '../../../../../utils/huePubSub';
+import { i18nReact } from '../../../../../utils/i18nReact';
+import formatBytes from '../../../../../utils/formatBytes';
+import useLoadData from '../../../../../utils/hooks/useLoadData';
+import { CONTENT_SUMMARY_API_URL } from '../../../../../reactComponents/FileChooser/api';
+import {
+  ContentSummary,
+  StorageDirectoryTableData
+} from '../../../../../reactComponents/FileChooser/types';
 
-import './SummaryModal.scss';
+import './ViewSummary.scss';
 
-interface SummaryModalProps {
-  path: string;
-  showModal: boolean;
+interface ViewSummaryProps {
+  path: StorageDirectoryTableData['path'];
+  isOpen?: boolean;
   onClose: () => void;
 }
 
-const SummaryModal = ({ showModal, onClose, path }: SummaryModalProps): JSX.Element => {
+const ViewSummary = ({ isOpen = true, onClose, path }: ViewSummaryProps): JSX.Element => {
   const { t } = i18nReact.useTranslation();
 
   const { data: responseSummary, loading } = useLoadData<ContentSummary>(CONTENT_SUMMARY_API_URL, {
-    params: { path },
+    params: { path: path },
     onError: error => {
       huePubSub.publish('hue.error', error);
     },
-    skip: path === '' || path === undefined || !showModal
+    skip: path === '' || path === undefined
   });
 
   const summary = [
@@ -75,14 +78,16 @@ const SummaryModal = ({ showModal, onClose, path }: SummaryModalProps): JSX.Elem
     { key: 'numberOfFiles', label: t('Number of Files'), value: responseSummary?.fileCount }
   ];
 
-  //TODO:Handle long modal title
+  const shortendPath =
+    path.split('/').length > 4 ? '...' + path.split('/').slice(-4).join('/') : path;
+
   return (
     <Modal
       className="hue-summary-modal cuix antd"
       okText={t('Close')}
       onOk={onClose}
-      open={showModal}
-      title={t('Summary for ') + path}
+      open={isOpen}
+      title={t('Summary for ') + shortendPath}
       cancellable={false}
       onCancel={onClose}
     >
@@ -100,4 +105,4 @@ const SummaryModal = ({ showModal, onClose, path }: SummaryModalProps): JSX.Elem
   );
 };
 
-export default SummaryModal;
+export default ViewSummary;