Explorar el Código

[ui-storagebrowser] chore: code cleanup (#3820)

* [ui-core]: feat: add useFetch hook for API calls

* add the license information

* pin the library version

* revert extra changes

* revert package-lock json

* fix: rename hook

* fix the function name

* [ui-storagebrowser] chore: move useHuePubSub to hooks folder

* [ui-core] enhance formatBytes util function

* [ui-storagebrowser] replace api calls with useLoadData hook

* [ui-storagebrowser] add mising data-event props in PathBrowser component
Ram Prasad Agarwal hace 1 año
padre
commit
f348cd7edd

+ 1 - 1
desktop/core/src/desktop/js/apps/editor/components/result/reactExample/ReactExample.tsx

@@ -14,7 +14,7 @@ import { Ace } from '../../../../../ext/ace';
 
 import { CURSOR_POSITION_CHANGED_EVENT } from '../../aceEditor/AceLocationHandler';
 import ReactExampleGlobal from '../../../../../reactComponents/ReactExampleGlobal/ReactExampleGlobal';
-import { useHuePubSub } from '../../../../../reactComponents/useHuePubSub';
+import { useHuePubSub } from '../../../../../utils/hooks/useHuePubSub';
 import SqlExecutable from '../../../execution/sqlExecutable';
 
 import './ReactExample.scss';

+ 16 - 28
desktop/core/src/desktop/js/apps/storageBrowser/StorageBrowserPage/StorageBrowserPage.tsx

@@ -14,7 +14,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import React, { useState, useEffect } from 'react';
+import React, { useMemo } from 'react';
 import { Tabs, Spin } from 'antd';
 import type { TabsProps } from 'antd';
 
@@ -23,39 +23,27 @@ import DataBrowserIcon from '@cloudera/cuix-core/icons/react/DataBrowserIcon';
 import { i18nReact } from '../../../utils/i18nReact';
 import CommonHeader from '../../../reactComponents/CommonHeader/CommonHeader';
 import StorageBrowserTabContent from './StorageBrowserTabContents/StorageBrowserTabContent';
-import { fetchFileSystems } from '../../../reactComponents/FileChooser/api';
+import { ApiFileSystem, FILESYSTEMS_API_URL } from '../../../reactComponents/FileChooser/api';
 
 import './StorageBrowserPage.scss';
+import useLoadData from '../../../utils/hooks/useLoadData';
 
 const StorageBrowserPage = (): JSX.Element => {
-  const [fileSystemTabs, setFileSystemTabs] = useState<TabsProps['items'] | undefined>();
-  const [loading, setLoading] = useState(true);
-
   const { t } = i18nReact.useTranslation();
 
-  useEffect(() => {
-    if (!fileSystemTabs) {
-      setLoading(true);
-      fetchFileSystems()
-        .then(fileSystems => {
-          const fileSystemsObj = fileSystems.map(system => {
-            return {
-              label: system.file_system.toUpperCase(),
-              key: system.file_system + '_tab',
-              children: <StorageBrowserTabContent user_home_dir={system.user_home_directory} />
-            };
-          });
-          setFileSystemTabs(fileSystemsObj);
-        })
-        .catch(error => {
-          //TODO: Properly handle errors.
-          console.error(error);
-        })
-        .finally(() => {
-          setLoading(false);
-        });
-    }
-  }, []);
+  const { data: fileSystems, loading } = useLoadData<ApiFileSystem[]>(FILESYSTEMS_API_URL);
+
+  const fileSystemTabs: TabsProps['items'] | undefined = useMemo(
+    () =>
+      fileSystems?.map(system => {
+        return {
+          label: system.file_system.toUpperCase(),
+          key: system.file_system + '_tab',
+          children: <StorageBrowserTabContent user_home_dir={system.user_home_directory} />
+        };
+      }),
+    [fileSystems]
+  );
 
   return (
     <div className="hue-storage-browser cuix antd">

+ 22 - 27
desktop/core/src/desktop/js/apps/storageBrowser/StorageBrowserPage/StorageBrowserTabContents/StorageBrowserTabContent.tsx

@@ -14,7 +14,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import React, { useState, useEffect, useCallback } from 'react';
+import React, { useState } from 'react';
 import { Spin } from 'antd';
 
 import { i18nReact } from '../../../../utils/i18nReact';
@@ -22,8 +22,10 @@ import BucketIcon from '@cloudera/cuix-core/icons/react/BucketIcon';
 
 import PathBrowser from '../../../../reactComponents/FileChooser/PathBrowser/PathBrowser';
 import StorageBrowserTable from '../StorageBrowserTable/StorageBrowserTable';
-import { fetchFiles } from '../../../../reactComponents/FileChooser/api';
+import { VIEWFILES_API_URl } from '../../../../reactComponents/FileChooser/api';
 import { PathAndFileData, SortOrder } from '../../../../reactComponents/FileChooser/types';
+import { DEFAULT_PAGE_SIZE } from '../../../../utils/constants/storageBrowser';
+import useLoadData from '../../../../utils/hooks/useLoadData';
 
 import './StorageBrowserTabContent.scss';
 
@@ -41,9 +43,7 @@ const StorageBrowserTabContent = ({
   testId
 }: StorageBrowserTabContentProps): JSX.Element => {
   const [filePath, setFilePath] = useState<string>(user_home_dir);
-  const [filesData, setFilesData] = useState<PathAndFileData>();
-  const [loadingFiles, setLoadingFiles] = useState(true);
-  const [pageSize, setPageSize] = useState<number>(0);
+  const [pageSize, setPageSize] = useState<number>(DEFAULT_PAGE_SIZE);
   const [pageNumber, setPageNumber] = useState<number>(1);
   const [sortByColumn, setSortByColumn] = useState<string>('');
   const [sortOrder, setSortOrder] = useState<SortOrder>(SortOrder.NONE);
@@ -51,28 +51,24 @@ const StorageBrowserTabContent = ({
 
   const { t } = i18nReact.useTranslation();
 
-  const getFiles = useCallback(async () => {
-    setLoadingFiles(true);
-    fetchFiles(filePath, pageSize, pageNumber, searchTerm, sortByColumn, sortOrder)
-      .then(responseFilesData => {
-        setFilesData(responseFilesData);
-        setPageSize(responseFilesData.pagesize);
-      })
-      .catch(error => {
-        //TODO: handle errors
-        console.error(error);
-      })
-      .finally(() => {
-        setLoadingFiles(false);
-      });
-  }, [filePath, pageSize, pageNumber, searchTerm, sortByColumn, sortOrder]);
-
-  useEffect(() => {
-    getFiles();
-  }, [getFiles]);
+  const {
+    data: filesData,
+    loading,
+    reloadData
+  } = useLoadData<PathAndFileData>(filePath, {
+    urlPrefix: VIEWFILES_API_URl,
+    params: {
+      pagesize: pageSize.toString(),
+      pagenum: pageNumber.toString(),
+      filter: searchTerm,
+      sortby: sortByColumn,
+      descending: sortOrder === SortOrder.DSC ? 'true' : 'false'
+    },
+    skip: filePath === '' || filePath === undefined
+  });
 
   return (
-    <Spin spinning={loadingFiles}>
+    <Spin spinning={loading}>
       <div className="hue-storage-browser-tabContent" data-testid={testId}>
         <div className="hue-storage-browser__title-bar" data-testid={`${testId}-title-bar`}>
           <BucketIcon className="hue-storage-browser__icon" data-testid={`${testId}-icon`} />
@@ -103,8 +99,7 @@ const StorageBrowserTabContent = ({
           onSearch={setSearchTerm}
           sortByColumn={sortByColumn}
           sortOrder={sortOrder}
-          refetchData={getFiles}
-          setLoadingFiles={setLoadingFiles}
+          refetchData={reloadData}
           filePath={filePath}
         />
       </div>

+ 39 - 38
desktop/core/src/desktop/js/apps/storageBrowser/StorageBrowserPage/StorageBrowserTable/StorageBrowserTable.tsx

@@ -16,7 +16,7 @@
 
 import React, { useEffect, useMemo, useState, useCallback } from 'react';
 import { ColumnProps } from 'antd/lib/table';
-import { Dropdown, Input } from 'antd';
+import { Dropdown, Input, Spin } from 'antd';
 import { MenuItemGroupType } from 'antd/lib/menu/hooks/useItems';
 import Tooltip from 'antd/es/tooltip';
 
@@ -63,7 +63,6 @@ interface StorageBrowserTableProps {
   sortOrder: SortOrder;
   rowClassName?: string;
   refetchData: () => void;
-  setLoadingFiles: (value: boolean) => void;
   testId?: string;
 }
 
@@ -88,10 +87,10 @@ const StorageBrowserTable = ({
   pageSize,
   rowClassName,
   refetchData,
-  setLoadingFiles,
   testId,
   ...restProps
 }: StorageBrowserTableProps): JSX.Element => {
+  const [loadingFiles, setLoadingFiles] = useState<boolean>(false);
   const [tableHeight, setTableHeight] = useState<number>();
   const [selectedFiles, setSelectedFiles] = useState<StorageBrowserTableData[]>([]);
   const [showNewFolderModal, setShowNewFolderModal] = useState<boolean>(false);
@@ -352,41 +351,43 @@ const StorageBrowserTable = ({
         </div>
       </div>
 
-      {viewType === BrowserViewType.dir && (
-        <Table
-          className={className}
-          columns={getColumns(tableData[0] ?? [])}
-          dataSource={tableData}
-          onRow={onRowClicked}
-          pagination={false}
-          rowClassName={rowClassName}
-          rowKey={(record, index) => record.path + '' + index}
-          rowSelection={{
-            type: 'checkbox',
-            ...rowSelection
-          }}
-          scroll={{ y: tableHeight }}
-          data-testid={`${testId}`}
-          locale={locale}
-          {...restProps}
-        />
-      )}
-
-      {viewType === BrowserViewType.file && (
-        // TODO: code for file view
-        <div> File view</div>
-      )}
-
-      {filesData?.page && (
-        <Pagination
-          onNextPageButtonClicked={onNextPageButtonClicked}
-          onPageNumberChange={onPageNumberChange}
-          onPageSizeChange={onPageSizeChange}
-          onPreviousPageButtonClicked={onPreviousPageButtonClicked}
-          pageSize={pageSize}
-          pageStats={filesData?.page}
-        />
-      )}
+      <Spin spinning={loadingFiles}>
+        {viewType === BrowserViewType.dir && (
+          <Table
+            className={className}
+            columns={getColumns(tableData[0] ?? [])}
+            dataSource={tableData}
+            onRow={onRowClicked}
+            pagination={false}
+            rowClassName={rowClassName}
+            rowKey={(record, index) => record.path + '' + index}
+            rowSelection={{
+              type: 'checkbox',
+              ...rowSelection
+            }}
+            scroll={{ y: tableHeight }}
+            data-testid={`${testId}`}
+            locale={locale}
+            {...restProps}
+          />
+        )}
+
+        {viewType === BrowserViewType.file && (
+          // TODO: code for file view
+          <div> File view</div>
+        )}
+
+        {filesData?.page && (
+          <Pagination
+            onNextPageButtonClicked={onNextPageButtonClicked}
+            onPageNumberChange={onPageNumberChange}
+            onPageSizeChange={onPageSizeChange}
+            onPreviousPageButtonClicked={onPreviousPageButtonClicked}
+            pageSize={pageSize}
+            pageStats={filesData?.page}
+          />
+        )}
+      </Spin>
 
       <InputModal
         title={t('Create New Folder')}

+ 30 - 56
desktop/core/src/desktop/js/reactComponents/FileChooser/FileChooserModal/FileChooserModal.tsx

@@ -14,7 +14,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useMemo } from 'react';
 import Modal from 'antd/lib/modal/Modal';
 import { Col, Menu, Row, Spin, Button } from 'antd';
 
@@ -22,10 +22,11 @@ import HdfsIcon from '../../../components/icons/HdfsIcon';
 import S3Icon from '../../../components/icons/S3Icon';
 import AdlsIcon from '../../../components/icons/AdlsIcon';
 
-import { fetchFileSystems, fetchFiles } from '../api';
+import { ApiFileSystem, FILESYSTEMS_API_URL, VIEWFILES_API_URl } from '../api';
 import { FileSystem, PathAndFileData } from '../types';
 import './FileChooserModal.scss';
 import PathBrowser from '../PathBrowser/PathBrowser';
+import useLoadData from '../../../utils/hooks/useLoadData';
 
 interface FileProps {
   show: boolean;
@@ -37,11 +38,7 @@ interface FileProps {
 const defaultProps = { title: 'Choose a file', okText: 'Select' };
 
 const FileChooserModal: React.FC<FileProps> = ({ show, onCancel, title, okText }) => {
-  const [fileSystemList, setFileSystemList] = useState<FileSystem[] | undefined>();
   const [filePath, setFilePath] = useState<string | undefined>();
-  const [filesData, setFilesData] = useState<PathAndFileData | undefined>();
-  const [loading, setLoading] = useState(true);
-  const [loadingFiles, setloadingFiles] = useState(true);
 
   const icons = {
     hdfs: <HdfsIcon />,
@@ -49,59 +46,36 @@ const FileChooserModal: React.FC<FileProps> = ({ show, onCancel, title, okText }
     s3: <S3Icon />
   };
 
-  const handleOk = () => {
-    //temporary until the file is selected through the file chooser component
-    onCancel();
-  };
+  //temporary until the file is selected through the file chooser component
+  const handleOk = onCancel;
+  const handleCancel = onCancel;
 
-  const handleCancel = () => {
-    onCancel();
-  };
+  const { data: fileSystemsData, loading: loadingFilesSystem } =
+    useLoadData<ApiFileSystem[]>(FILESYSTEMS_API_URL);
 
-  useEffect(() => {
-    if (show && !fileSystemList) {
-      setLoading(true);
-      fetchFileSystems()
-        .then(fileSystems => {
-          const fileSystemsObj = fileSystems.map((system, index) => {
-            return {
-              label: system.file_system,
-              key: index,
-              icon: icons[system.file_system],
-              user_home_dir: system.user_home_directory
-            };
-          });
-          setFileSystemList(fileSystemsObj);
-          if (fileSystems.length !== 0) {
-            setFilePath(fileSystems[0].user_home_directory);
-          }
-        })
-        .catch(error => {
-          //TODO: Properly handle errors.
-          console.error(error);
-        })
-        .finally(() => {
-          setLoading(false);
-        });
-    }
-  }, [show]);
+  const fileSystemList: FileSystem[] | undefined = useMemo(
+    () =>
+      fileSystemsData?.map((system, index) => {
+        return {
+          label: system.file_system,
+          key: index,
+          icon: icons[system.file_system],
+          user_home_dir: system.user_home_directory
+        };
+      }),
+    [fileSystemsData]
+  );
 
   useEffect(() => {
-    if (filePath) {
-      setloadingFiles(true);
-      fetchFiles(filePath)
-        .then(responseFilesData => {
-          setFilesData(responseFilesData);
-        })
-        .catch(error => {
-          //TODO: handle errors
-          console.error(error);
-        })
-        .finally(() => {
-          setloadingFiles(false);
-        });
+    if (fileSystemsData && fileSystemsData?.length !== 0) {
+      setFilePath(fileSystemsData[0].user_home_directory);
     }
-  }, [filePath]);
+  }, [fileSystemsData]);
+
+  const { data: filesData, loading: loadingFiles } = useLoadData<PathAndFileData>(filePath, {
+    urlPrefix: VIEWFILES_API_URl,
+    skip: !!filePath
+  });
 
   return (
     <Modal
@@ -113,13 +87,13 @@ const FileChooserModal: React.FC<FileProps> = ({ show, onCancel, title, okText }
       width={930}
       className="hue-file-chooser__modal"
     >
-      <Spin spinning={loading}>
+      <Spin spinning={loadingFilesSystem || loadingFiles}>
         <Row>
           <Col span={5}>
             <Menu
               items={fileSystemList}
               onSelect={selectedMenuItem => {
-                setFilePath(fileSystemList[selectedMenuItem.key].user_home_dir);
+                setFilePath(fileSystemList?.[selectedMenuItem.key].user_home_dir);
               }}
               className="hue-file-system__panel"
             ></Menu>

+ 4 - 0
desktop/core/src/desktop/js/reactComponents/FileChooser/PathBrowser/PathBrowser.tsx

@@ -156,6 +156,7 @@ const PathBrowser = ({
                     data-testid={`${testId}-dropdown`}
                   >
                     <BorderlessButton
+                      data-event=""
                       className="hue-path-browser__dropdown-button"
                       data-testid={`${testId}-dropdown-btn`}
                     >
@@ -190,6 +191,7 @@ const PathBrowser = ({
               )}
             </div>
             <BorderlessButton
+              data-event=""
               className="hue-path-browser__toggle-breadcrumb-input-btn"
               aria-label="hue-path-browser__toggle-breadcrumb-input-btn"
               title="Edit path"
@@ -216,6 +218,8 @@ const PathBrowser = ({
       </>
     );
   }
+
+  return <></>;
 };
 
 PathBrowser.defaultProps = defaultProps;

+ 3 - 43
desktop/core/src/desktop/js/reactComponents/FileChooser/api.ts

@@ -15,10 +15,10 @@
 // limitations under the License.
 import { get, post } from '../../api/utils';
 import { CancellablePromise } from '../../api/cancellablePromise';
-import { PathAndFileData, ContentSummary, SortOrder } from './types';
+import { ContentSummary } from './types';
 
-const FILESYSTEMS_API_URL = '/api/v1/storage/filesystems';
-const VIEWFILES_API_URl = '/api/v1/storage/view=';
+export const FILESYSTEMS_API_URL = '/api/v1/storage/filesystems';
+export const VIEWFILES_API_URl = '/api/v1/storage/view=';
 const MAKE_DIRECTORY_API_URL = '/api/v1/storage/mkdir';
 const TOUCH_API_URL = '/api/v1/storage/touch';
 const CONTENT_SUMMARY_API_URL = '/api/v1/storage/content_summary=';
@@ -29,46 +29,6 @@ export interface ApiFileSystem {
   user_home_directory: string;
 }
 
-export const fetchFileSystems = (): CancellablePromise<ApiFileSystem[]> => get(FILESYSTEMS_API_URL);
-
-//TODO: Use object as parameter instead
-export const fetchFiles = (
-  filePath: string,
-  pagesize?: number,
-  pagenum?: number,
-  filter?: string,
-  sortby?: string,
-  sortOrder?: SortOrder
-): CancellablePromise<PathAndFileData> => {
-  let descending = false;
-  if (sortOrder === SortOrder.ASC) {
-    descending = false;
-  } else if (sortOrder === SortOrder.DSC) {
-    descending = true;
-  } else {
-    sortby = '';
-  }
-  //If value is undefined default value is assigned.
-  pagesize = pagesize || 10;
-  pagenum = pagenum || 1;
-  filter = filter || '';
-  sortby = sortby || '';
-  return get(
-    VIEWFILES_API_URl +
-      filePath +
-      '?pagesize=' +
-      pagesize +
-      '&pagenum=' +
-      pagenum +
-      '&filter=' +
-      filter +
-      '&sortby=' +
-      sortby +
-      '&descending=' +
-      descending
-  );
-};
-
 export const mkdir = async (folderName: string, path: string): Promise<void> => {
   await post(MAKE_DIRECTORY_API_URL, { name: folderName, path: path });
 };

+ 1 - 1
desktop/core/src/desktop/js/reactComponents/GlobalAlert/GlobalAlert.tsx

@@ -26,7 +26,7 @@ import {
   HIDE_GLOBAL_ALERTS_TOPIC
 } from './events';
 import { HueAlert } from './types';
-import { useHuePubSub } from '../useHuePubSub';
+import { useHuePubSub } from '../../utils/hooks/useHuePubSub';
 import { i18nReact } from 'utils/i18nReact';
 
 type alertType = AlertProps['type'];

+ 1 - 1
desktop/core/src/desktop/js/reactComponents/WelcomeTour/WelcomeTour.tsx

@@ -19,7 +19,7 @@ import Joyride from 'react-joyride';
 
 import { hueWindow } from 'types/types';
 import I18n from 'utils/i18n';
-import { useHuePubSub } from '../useHuePubSub';
+import { useHuePubSub } from '../../utils/hooks/useHuePubSub';
 
 import './WelcomeTour.scss';
 import scssVariables from './WelcomeTour.module.scss';

+ 1 - 23
desktop/core/src/desktop/js/reactComponents/FileChooser/api.test.ts → desktop/core/src/desktop/js/utils/constants/storageBrowser.ts

@@ -14,26 +14,4 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import { CancellablePromise } from '../../api/cancellablePromise';
-import * as ApiUtils from '../../api/utils';
-import { fetchFileSystems } from './api';
-
-describe('tests the filesystems api', () => {
-  it('test for valid filesystem api response', async () => {
-    const mockData = [
-      { file_system: 'hdfs', user_home_directory: '/user/demo' },
-      { file_system: 'abfs', user_home_directory: 'abfs://jahlenc' }
-    ];
-
-    const getSpy = jest
-      .spyOn(ApiUtils, 'get')
-      .mockImplementation(() => CancellablePromise.resolve(mockData));
-
-    const filesystems = await fetchFileSystems();
-
-    expect(getSpy).toHaveBeenCalledWith('/api/v1/storage/filesystems');
-    expect(filesystems).toEqual(mockData);
-  });
-
-  //TODO: tests for errors
-});
+export const DEFAULT_PAGE_SIZE = 50;

+ 8 - 0
desktop/core/src/desktop/js/utils/formatBytes.test.ts

@@ -24,6 +24,14 @@ describe('formatBytes function', () => {
     expect(formatBytes(0)).toBe('0 Byte');
   });
 
+  test('returns "17 Byte" when bytes is 17', () => {
+    expect(formatBytes(17)).toBe('17 Bytes');
+  });
+
+  test('returns "18 Byte" when bytes is 17.98', () => {
+    expect(formatBytes(17.98)).toBe('18 Bytes');
+  });
+
   test('correctly formats bytes to KB', () => {
     expect(formatBytes(1024)).toBe('1.00 KB');
     expect(formatBytes(2048)).toBe('2.00 KB');

+ 4 - 4
desktop/core/src/desktop/js/utils/formatBytes.ts

@@ -14,17 +14,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-const formatBytes = (bytes: number, decimalPoints?: number): string => {
-  if (bytes == -1) {
+const formatBytes = (bytes?: number, decimalPoints: number = 2): string => {
+  if (bytes === -1 || bytes == undefined) {
     return 'Not available';
   }
-  if (bytes == 0) {
+  if (bytes === 0) {
     return '0 Byte';
   }
   const k = 1024;
-  const dm = decimalPoints ? decimalPoints : 2;
   const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
   const i = Math.floor(Math.log(bytes) / Math.log(k));
+  const dm = i === 0 ? 0 : decimalPoints; // Don't show decimal points for Bytes
   return (bytes / Math.pow(k, i)).toFixed(dm) + ' ' + sizes[i];
 };
 

+ 2 - 2
desktop/core/src/desktop/js/reactComponents/useHuePubSub.test.tsx → desktop/core/src/desktop/js/utils/hooks/useHuePubSub.test.tsx

@@ -1,7 +1,7 @@
 import { renderHook, act } from '@testing-library/react';
 import { useHuePubSub } from './useHuePubSub';
-import huePubSub from '../utils/huePubSub';
-import noop from '../utils/timing/noop';
+import huePubSub from '../huePubSub';
+import noop from '../timing/noop';
 
 describe('useHuePubSub', () => {
   const originalSubscribe = huePubSub.subscribe;

+ 1 - 1
desktop/core/src/desktop/js/reactComponents/useHuePubSub.ts → desktop/core/src/desktop/js/utils/hooks/useHuePubSub.ts

@@ -15,7 +15,7 @@
 // limitations under the License.
 
 import { useState, useEffect } from 'react';
-import huePubSub from '../utils/huePubSub';
+import huePubSub from '../huePubSub';
 
 // Basic helper hook to let a component subscribe to a huePubSub topic and rerender for each message
 // by placing the message/info in a state that is automatically updated.