Procházet zdrojové kódy

[frontend]Storage Browser initial layout implementation (#3397)

* [frontend]Storage Browser initial layout implementation

* [frontend]Fixed python styling

* [frontend]Changed feature flag to enable_new_storage_browser

* [frontend]Moved models.py changes to UI and improvements

* [frontend]Added data-browser icon and improvements
Nidhi Bhat G před 2 roky
rodič
revize
0c4719595b

+ 31 - 0
apps/filebrowser/src/filebrowser/templates/storage_browser.mako

@@ -0,0 +1,31 @@
+## -*- coding: utf-8 -*-
+## 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.
+
+<%!
+  from desktop.webpack_utils import get_hue_bundles
+  from webpack_loader.templatetags.webpack_loader import render_bundle
+%>
+
+% for bundle in get_hue_bundles('storageBrowser'):
+    ${ render_bundle(bundle) | n,unicode }
+% endfor
+
+<div>
+  <StorageBrowserPage data-reactcomponent='StorageBrowserPage'></StorageBrowserPage>
+</div>
+
+

+ 4 - 0
apps/filebrowser/src/filebrowser/views.py

@@ -48,6 +48,7 @@ from aws.s3.s3fs import S3FileSystemException, S3ListAllBucketsException, get_s3
 from desktop import appmanager
 from desktop.auth.backend import is_admin
 from desktop.conf import RAZ
+from desktop.conf import ENABLE_NEW_STORAGE_BROWSER
 from desktop.lib import fsmanager
 from desktop.lib import i18n
 from desktop.lib.conf import coerce_bool
@@ -520,6 +521,9 @@ def listdir_paged(request, path):
     filter=?          - Specify a substring filter to search for in
                         the filename field.
   """
+  if ENABLE_NEW_STORAGE_BROWSER.get():
+    return render('storage_browser.mako', request, {})
+
   path = _normalize_path(path)
 
   if not request.fs.isdir(path):

+ 2 - 0
desktop/core/src/desktop/api2.py

@@ -48,6 +48,7 @@ from useradmin.models import User, Group
 from desktop import appmanager
 from desktop.auth.backend import is_admin
 from desktop.conf import ENABLE_CONNECTORS, ENABLE_GIST_PREVIEW, CUSTOM, get_clusters, IS_K8S_ONLY, ENABLE_SHARING
+from desktop.conf import ENABLE_NEW_STORAGE_BROWSER
 from desktop.lib.conf import BoundContainer, GLOBAL_CONFIG, is_anonymous
 from desktop.lib.django_util import JsonResponse, login_notrequired, render
 from desktop.lib.exceptions_renderable import PopupException
@@ -99,6 +100,7 @@ def get_config(request):
   config = get_cluster_config(request.user)
   config['hue_config']['is_admin'] = is_admin(request.user)
   config['hue_config']['is_yarn_enabled'] = is_yarn()
+  config['hue_config']['enable_new_storage_browser'] = ENABLE_NEW_STORAGE_BROWSER.get()
   config['clusters'] = list(get_clusters(request.user).values())
   config['documents'] = {
     'types': list(Document2.objects.documents(user=request.user).order_by().values_list('type', flat=True).distinct())

+ 7 - 0
desktop/core/src/desktop/conf.py

@@ -1811,6 +1811,13 @@ ENABLE_HUE_5 = Config(
   default=False
 )
 
+ENABLE_NEW_STORAGE_BROWSER = Config(
+  key="enable_new_storage_browser",
+  help=_("Feature flag to enable new Hue Storage browser."),
+  type=coerce_bool,
+  default=False
+)
+
 USE_NEW_EDITOR = Config( # To remove in Hue 4
   key='',
   default=True,

+ 34 - 0
desktop/core/src/desktop/js/apps/storageBrowser/StorageBrowserPage/StorageBrowserPage.tsx

@@ -0,0 +1,34 @@
+// 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 CommonHeader from '../../../reactComponents/CommonHeader/CommonHeader';
+import DataBrowserIcon from '@cloudera/cuix-core/icons/react/DataBrowserIcon';
+
+const StorageBrowserPage: React.FC = (): JSX.Element => {
+  const { t } = i18nReact.useTranslation();
+
+  return (
+    <div className="hue-storage-browser">
+      <CommonHeader title={t('Storage Browser')} icon={<DataBrowserIcon />} />
+    </div>
+  );
+};
+
+export default StorageBrowserPage;

+ 23 - 0
desktop/core/src/desktop/js/apps/storageBrowser/app.js

@@ -0,0 +1,23 @@
+// 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 huePubSub from '../../utils/huePubSub';
+
+huePubSub.subscribe('app.dom.loaded', app => {
+  if (app !== 'filebrowser') {
+    return;
+  }
+  window.createReactComponents('#embeddable_filebrowser');
+});

+ 30 - 2
desktop/core/src/desktop/js/components/sidebar/HueSidebar.vue

@@ -54,7 +54,7 @@
     ASSIST_ACTIVE_DB_CHANGED_EVENT,
     ASSIST_SET_DATABASE_EVENT
   } from 'ko/components/assist/events';
-  import { AppType, Connector, HueConfig, Namespace } from 'config/types';
+  import { AppType, Connector, HueConfig, Namespace, BrowserInterpreter } from 'config/types';
   import { hueWindow } from 'types/types';
   import { CONFIG_REFRESHED_TOPIC } from 'config/events';
   import { getLastKnownConfig } from 'config/hueConfig';
@@ -79,6 +79,7 @@
     abfs: `<svg class="hi hi-fw"><use xlink:href="#hi-adls"></use></svg>`,
     adls: `<svg class="hi hi-fw"><use xlink:href="#hi-adls"></use></svg>`,
     dashboard: `<svg class="hi hi-fw"><use xlink:href="#hi-dashboard"></use></svg>`,
+    'data-browser': `<svg class="hi hi-fw"><use xlink:href="#hi-data-browser"></use></svg>`,
     default: `<i class="fa fa-fw fa-database"></i>`,
     'dist-cp': `<i class="fa fa-fw fa-files-o"></i>`,
     documents: `<svg class="hi hi-fw"><use xlink:href="#hi-documents"></use></svg>`,
@@ -484,7 +485,34 @@
             });
           }
           if (appConfig.browser && appConfig.browser.interpreters) {
-            appConfig.browser.interpreters.forEach(browser => {
+            // Replace old file browser entries with the new storage browser if the feature flag is enabled.
+            let browserInterpreters: BrowserInterpreter[] = [];
+            if (clusterConfig.hue_config.enable_new_storage_browser) {
+              let firstFileBrowserFound = false;
+              appConfig.browser.interpreters.forEach(browser => {
+                const isFileBrowser = /\/filebrowser/.test(browser.page);
+                if (isFileBrowser) {
+                  if (!firstFileBrowserFound) {
+                    browserInterpreters.push({
+                      buttonName: 'Browse',
+                      //TODO: Get i18n here
+                      displayName: 'Storage Browser',
+                      //by default the first filesystem's url in the config is used.
+                      page: browser.page,
+                      tooltip: 'Storage Browser',
+                      type: 'data-browser'
+                    });
+                  }
+                  firstFileBrowserFound = true;
+                } else {
+                  browserInterpreters.push(browser);
+                }
+              });
+            } else {
+              browserInterpreters = appConfig.browser.interpreters;
+            }
+
+            browserInterpreters.forEach(browser => {
               if (browser.type === 'tables') {
                 browserItems.push({
                   type: 'navigation',

+ 41 - 0
desktop/core/src/desktop/js/reactComponents/CommonHeader/CommonHeader.scss

@@ -0,0 +1,41 @@
+// 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;
+
+$heading-font-family: Roboto, 'Helvetica Neue', Helvetica, Arial, sans-serif;
+
+.hue-common-header {
+  background-color: vars.$fluidx-gray-100;
+  height: 32px;
+  padding: 16px;
+  display: flex;
+
+  .hue-header-icon {
+    margin-right: 10px;
+    flex-shrink: 0;
+    height: 24px;
+    width: 24px;
+  }
+
+  .hue-header-title {
+    font-family: $heading-font-family;
+    font-size: vars.$fluidx-heading-h3-size;
+    line-height: vars.$fluidx-heading-h3-line-height;
+    font-weight: vars.$fluidx-heading-h3-weight;
+    color: vars.$text-color;
+  }
+}

+ 30 - 0
desktop/core/src/desktop/js/reactComponents/CommonHeader/CommonHeader.test.tsx

@@ -0,0 +1,30 @@
+// 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, screen, within } from '@testing-library/react';
+import '@testing-library/jest-dom';
+
+import HdfsIcon from '../../components/icons/HdfsIcon';
+import CommonHeader from './CommonHeader';
+
+describe('CommonHeader', () => {
+  test('render header with custom title', () => {
+    render(<CommonHeader title="File Browser" icon={<HdfsIcon />} />);
+    const header = screen.getByTestId('hue-header-title');
+    expect(within(header).getByText('File Browser')).toBeVisible();
+  });
+});

+ 47 - 0
desktop/core/src/desktop/js/reactComponents/CommonHeader/CommonHeader.tsx

@@ -0,0 +1,47 @@
+// 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 './CommonHeader.scss';
+
+interface CommonHeaderProps {
+  title: string;
+  icon: JSX.Element;
+  testId?: string;
+}
+
+const defaultProps = {
+  testId: 'hue-header'
+};
+
+const CommonHeader: React.FC<CommonHeaderProps> = ({ title, icon, testId }) => {
+  return (
+    <div className="hue-common-header" data-testid={testId}>
+      <div className="hue-header-icon" data-testid={`${testId}-icon`}>
+        {icon}
+      </div>
+      <div className="hue-header-title" data-testid={`${testId}-title`}>
+        {title}
+      </div>
+      {/* TODO: Add actions dropdown*/}
+    </div>
+  );
+};
+
+CommonHeader.defaultProps = defaultProps;
+
+export default CommonHeader;

+ 3 - 0
desktop/core/src/desktop/js/reactComponents/imports.js

@@ -7,6 +7,9 @@ export async function loadComponent(name) {
     case 'ReactExample':
       return (await import('../apps/editor/components/result/reactExample/ReactExample')).default;
 
+    case 'StorageBrowserPage':
+      return (await import('../apps/storageBrowser/StorageBrowserPage/StorageBrowserPage')).default;
+
     // Application global components here
     case 'AppBanner':
       return (await import('./AppBanner/AppBanner')).default;

+ 5 - 0
desktop/core/src/desktop/templates/hue_icons.mako

@@ -44,6 +44,10 @@
       <rect x="517.99" y="258.58" width="119.71" height="361.44"></rect>
     </symbol>
 
+    <symbol id="hi-data-browser" viewBox="0 0 24 24">
+      <path d="M13.65 11.112a3.031 3.031 0 010 6.062 3.03 3.03 0 01-3.021-3.036 3.028 3.028 0 013.021-3.026zm4.135 5.79a4.973 4.973 0 00-4.135-7.729c-2.73 0-4.957 2.23-4.957 4.965 0 2.745 2.227 4.976 4.957 4.976a4.961 4.961 0 002.76-.834l3.215 3.22L21 20.123l-3.215-3.221zM5.198 4.799c.735-.282 2.236-.592 4.464-.592 2.226 0 3.727.31 4.463.592-2.372.765-6.555.765-8.927 0zM9.662 2.5C7.657 2.5 3 2.752 3 5.002v10.281c0 .95.833 1.648 2.459 2.056.959.243 2.188.398 3.515.436a5.88 5.88 0 01-.939-1.755c-1.976-.155-3.099-.562-3.331-.815v-1.881c.871.281 1.917.475 3.03.572.02-.505.098-.979.242-1.445-1.268-.098-2.439-.33-3.272-.679V9.648c1.219.398 2.807.62 4.425.659a6.107 6.107 0 011.926-1.503c-2.295.164-4.86-.078-6.351-.699V6.166c1.355.446 3.156.669 4.958.669 1.8 0 3.601-.223 4.957-.669V8.29c.29.039.581.106.852.204.3.097.58.213.852.348v-3.84c0-2.25-4.657-2.502-6.661-2.502z"></path>
+    </symbol>
+
     <symbol id="hi-documents" viewBox="0 0 640 640">
       <path d="M106.54,602.44A36.4,36.4,0,0,1,71,574.3L1,278.19a36.54,36.54,0,0,1,35.56-44.94h47.5V189a33.29,33.29,0,0,1,33.25-33.25H217.62l22.15-91.63a34.55,34.55,0,0,1,41.57-25.59L492.62,88.43a34.76,34.76,0,0,1,21.77,16l30.48,51.29h58.6A36.58,36.58,0,0,1,640,192.24V564.59a36.3,36.3,0,0,1-10.7,25.84l-1.9,1.9A34.31,34.31,0,0,1,603,602.44Zm476.33-41L530.36,339.24H259a36.41,36.41,0,0,1-35.56-28.13l-8.71-36.84H42.21l67.87,287.16ZM561.14,310.37a36.53,36.53,0,0,1,8.32,16L599,451.31V196.72H569.24l6.22,10.46a34.4,34.4,0,0,1,3.92,25.61Zm-39.27-12.15,9-38.1L449,240.79a34.61,34.61,0,0,1-25.69-41.59l19-80.51L278.15,79.88,239.44,240a36.67,36.67,0,0,1,14.42,21.43l8.71,36.84Zm-323-65,8.83-36.53H125.06v36.53ZM534.71,218.9l-52.86-89-17.12,72.42Z"></path>
     </symbol>
@@ -565,6 +569,7 @@
   <script type="text/html" id="app-icon-template">
     <!-- ko switch: icon -->
     <!-- ko case: 'dashboard' --><svg class="hi hi-fw"><use xlink:href="#hi-dashboard"></use></svg><!-- /ko -->
+    <!-- ko case: 'data-browser' --><svg class="hi hi-fw"><use xlink:href="#hi-data-browser"></use></svg><!-- /ko -->
     <!-- ko case: 'documents' --><svg class="hi hi-fw"><use xlink:href="#hi-documents"></use></svg><!-- /ko -->
     <!-- ko case: 'report' --><i class="fa fa-fw fa-area-chart"></i><!-- /ko -->
     <!-- ko case: 'dist-cp' --><i class="fa fa-fw fa-files-o"></i><!-- /ko -->

+ 5 - 1
webpack.config.js

@@ -33,11 +33,15 @@ const config = {
       import: './desktop/core/src/desktop/js/apps/tableBrowser/app.js',
       dependOn: 'hue'
     },
+    storageBrowser: {
+      import: './desktop/core/src/desktop/js/apps/storageBrowser/app.js',
+      dependOn: 'hue'
+    },
     jobBrowser: { import: './desktop/core/src/desktop/js/apps/jobBrowser/app.js', dependOn: 'hue' }
   },
   mode: 'development',
   module: {
-    rules: [    
+    rules: [
       {
         test: /\.vue$/,
         exclude: /node_modules/,