Browse Source

[ui-storagebrowser] add download button for files (#3851)

* [ui-storagebrowser] add download button for files

* add test for download button

* remove iframe and download using invisible link

* remove unused mock doe downloadFile

* use actual link tag rather than adding it by script
Ram Prasad Agarwal 1 year ago
parent
commit
cef1bae6f9

+ 64 - 1
desktop/core/src/desktop/js/apps/storageBrowser/StorageFilePage/StorageFilePage.test.tsx

@@ -1,9 +1,27 @@
+// 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 React from 'react';
 import { render, screen, waitFor } from '@testing-library/react';
 import { render, screen, waitFor } from '@testing-library/react';
 import StorageFilePage from './StorageFilePage';
 import StorageFilePage from './StorageFilePage';
 import { PathAndFileData } from '../../../reactComponents/FileChooser/types';
 import { PathAndFileData } from '../../../reactComponents/FileChooser/types';
 import '@testing-library/jest-dom';
 import '@testing-library/jest-dom';
 import userEvent from '@testing-library/user-event';
 import userEvent from '@testing-library/user-event';
+import { DOWNLOAD_API_URL } from '../../../reactComponents/FileChooser/api';
+import huePubSub from '../../../utils/huePubSub';
 
 
 jest.mock('../../../utils/dateTimeUtils', () => ({
 jest.mock('../../../utils/dateTimeUtils', () => ({
   ...jest.requireActual('../../../utils/dateTimeUtils'),
   ...jest.requireActual('../../../utils/dateTimeUtils'),
@@ -12,6 +30,10 @@ jest.mock('../../../utils/dateTimeUtils', () => ({
   }
   }
 }));
 }));
 
 
+jest.mock('../../../utils/huePubSub', () => ({
+  publish: jest.fn()
+}));
+
 // Mock data for fileData
 // Mock data for fileData
 const mockFileData: PathAndFileData = {
 const mockFileData: PathAndFileData = {
   path: '/path/to/file.txt',
   path: '/path/to/file.txt',
@@ -40,7 +62,8 @@ const mockFileData: PathAndFileData = {
     end_index: 1,
     end_index: 1,
     total_count: 1
     total_count: 1
   },
   },
-  pagesize: 100
+  pagesize: 100,
+  show_download_button: true
 };
 };
 
 
 describe('StorageFilePage', () => {
 describe('StorageFilePage', () => {
@@ -128,4 +151,44 @@ describe('StorageFilePage', () => {
     expect(screen.queryByRole('button', { name: 'Cancel' })).toBeNull();
     expect(screen.queryByRole('button', { name: 'Cancel' })).toBeNull();
     expect(screen.getByRole('button', { name: 'Edit' })).toBeVisible();
     expect(screen.getByRole('button', { name: 'Edit' })).toBeVisible();
   });
   });
+
+  it('downloads file when download button is clicked', async () => {
+    const user = userEvent.setup();
+    render(<StorageFilePage fileData={mockFileData} />);
+
+    await user.click(screen.getByRole('button', { name: 'Download' }));
+
+    expect(huePubSub.publish).toHaveBeenCalledWith('hue.global.info', {
+      message: 'Downloading your file, Please wait...'
+    });
+
+    const downloadLink = screen.getByRole('link', { name: 'Download' });
+    expect(downloadLink).toHaveAttribute('href', `${DOWNLOAD_API_URL}${mockFileData.path}`);
+  });
+
+  it('downloads file when download button is clicked', async () => {
+    const user = userEvent.setup();
+    render(<StorageFilePage fileData={mockFileData} />);
+
+    await user.click(screen.getByRole('button', { name: 'Download' }));
+
+    expect(huePubSub.publish).toHaveBeenCalledWith('hue.global.info', {
+      message: 'Downloading your file, Please wait...'
+    });
+
+    const downloadLink = screen.getByRole('link', { name: 'Download' });
+    expect(downloadLink).toHaveAttribute('href', `${DOWNLOAD_API_URL}${mockFileData.path}`);
+  });
+
+  it('does not render the download button when show_download_button is false', () => {
+    const mockFileDataWithoutDownload = {
+      ...mockFileData,
+      show_download_button: false
+    };
+
+    render(<StorageFilePage fileData={mockFileDataWithoutDownload} />);
+
+    expect(screen.queryByRole('button', { name: 'Download' })).toBeNull();
+    expect(screen.queryByRole('link', { name: 'Download' })).toBeNull();
+  });
 });
 });

+ 15 - 1
desktop/core/src/desktop/js/apps/storageBrowser/StorageFilePage/StorageFilePage.tsx

@@ -20,6 +20,8 @@ import './StorageFilePage.scss';
 import { i18nReact } from '../../../utils/i18nReact';
 import { i18nReact } from '../../../utils/i18nReact';
 import Button, { PrimaryButton } from 'cuix/dist/components/Button';
 import Button, { PrimaryButton } from 'cuix/dist/components/Button';
 import { getFileMetaData } from './StorageFilePage.util';
 import { getFileMetaData } from './StorageFilePage.util';
+import { DOWNLOAD_API_URL } from '../../../reactComponents/FileChooser/api';
+import huePubSub from '../../../utils/huePubSub';
 
 
 const StorageFilePage = ({ fileData }: { fileData: PathAndFileData }): JSX.Element => {
 const StorageFilePage = ({ fileData }: { fileData: PathAndFileData }): JSX.Element => {
   const { t } = i18nReact.useTranslation();
   const { t } = i18nReact.useTranslation();
@@ -31,6 +33,10 @@ const StorageFilePage = ({ fileData }: { fileData: PathAndFileData }): JSX.Eleme
     setIsEditing(true);
     setIsEditing(true);
   };
   };
 
 
+  const handleDownload = () => {
+    huePubSub.publish('hue.global.info', { message: t('Downloading your file, Please wait...') });
+  };
+
   const handleSave = () => {
   const handleSave = () => {
     // TODO: Save file content to API
     // TODO: Save file content to API
     setIsEditing(false);
     setIsEditing(false);
@@ -86,11 +92,19 @@ const StorageFilePage = ({ fileData }: { fileData: PathAndFileData }): JSX.Eleme
             >
             >
               {t('Cancel')}
               {t('Cancel')}
             </Button>
             </Button>
+            <a href={`${DOWNLOAD_API_URL}${fileData.path}`} hidden={!fileData.show_download_button}>
+              <PrimaryButton
+                data-testid="preview--download--button"
+                data-event=""
+                onClick={handleDownload}
+              >
+                {t('Download')}
+              </PrimaryButton>
+            </a>
           </div>
           </div>
         </div>
         </div>
 
 
         <textarea
         <textarea
-          data-testid="file-content"
           value={fileContent}
           value={fileContent}
           onChange={e => setFileContent(e.target.value)}
           onChange={e => setFileContent(e.target.value)}
           readOnly={!isEditing}
           readOnly={!isEditing}

+ 1 - 0
desktop/core/src/desktop/js/reactComponents/FileChooser/api.ts

@@ -19,6 +19,7 @@ import { ContentSummary } from './types';
 
 
 export const FILESYSTEMS_API_URL = '/api/v1/storage/filesystems';
 export const FILESYSTEMS_API_URL = '/api/v1/storage/filesystems';
 export const VIEWFILES_API_URl = '/api/v1/storage/view=';
 export const VIEWFILES_API_URl = '/api/v1/storage/view=';
+export const DOWNLOAD_API_URL = '/filebrowser/download=';
 const MAKE_DIRECTORY_API_URL = '/api/v1/storage/mkdir';
 const MAKE_DIRECTORY_API_URL = '/api/v1/storage/mkdir';
 const TOUCH_API_URL = '/api/v1/storage/touch';
 const TOUCH_API_URL = '/api/v1/storage/touch';
 const CONTENT_SUMMARY_API_URL = '/api/v1/storage/content_summary=';
 const CONTENT_SUMMARY_API_URL = '/api/v1/storage/content_summary=';

+ 1 - 0
desktop/core/src/desktop/js/reactComponents/FileChooser/types.ts

@@ -86,6 +86,7 @@ export interface PathAndFileData {
   stats: Stats;
   stats: Stats;
   rwx: string;
   rwx: string;
   view: FileView;
   view: FileView;
+  show_download_button: boolean;
 }
 }
 
 
 export interface ContentSummary {
 export interface ContentSummary {