Răsfoiți Sursa

HUE-9289 [editor] Fix editor context popover for files with a root path other than /

Johan Ahlen 5 ani în urmă
părinte
comite
c5d920c60e

+ 3 - 3
desktop/core/src/desktop/js/apps/notebook/editorViewModel.js

@@ -29,7 +29,7 @@ import {
   ACTIVE_SNIPPET_CONNECTOR_CHANGED_EVENT,
   GET_ACTIVE_SNIPPET_CONNECTOR_EVENT
 } from 'apps/notebook2/events';
-import { findConnector } from 'utils/hueConfig';
+import { findEditorConnector } from 'utils/hueConfig';
 
 class EditorViewModel {
   constructor(editor_id, notebooks, options, CoordinatorEditorViewModel, RunningCoordinatorModel) {
@@ -52,7 +52,7 @@ class EditorViewModel {
 
     const updateConnector = type => {
       if (type) {
-        self.activeConnector(findConnector(connector => connector.type === type));
+        self.activeConnector(findEditorConnector(connector => connector.type === type));
       }
     };
 
@@ -609,7 +609,7 @@ class EditorViewModel {
       const type = editorType || options.editor_type;
 
       if (!self.isNotificationManager()) {
-        self.activeConnector(findConnector(connector => connector.type === type));
+        self.activeConnector(findEditorConnector(connector => connector.type === type));
         huePubSub.publish(ACTIVE_SNIPPET_CONNECTOR_CHANGED_EVENT, self.activeConnector());
       }
 

+ 2 - 2
desktop/core/src/desktop/js/apps/notebook/snippet.js

@@ -31,7 +31,7 @@ import sqlStatementsParser from 'parse/sqlStatementsParser';
 import { SHOW_EVENT as SHOW_GIST_MODAL_EVENT } from 'ko/components/ko.shareGistModal';
 import { cancelActiveRequest } from 'api/apiUtils';
 import { ACTIVE_SNIPPET_CONNECTOR_CHANGED_EVENT } from 'apps/notebook2/events';
-import { findConnector } from 'utils/hueConfig';
+import { findEditorConnector } from 'utils/hueConfig';
 import {
   ASSIST_GET_DATABASE_EVENT,
   ASSIST_GET_SOURCE_EVENT,
@@ -187,7 +187,7 @@ class Snippet {
 
     const updateConnector = type => {
       if (type) {
-        self.connector(findConnector(connector => connector.type === type));
+        self.connector(findEditorConnector(connector => connector.type === type));
       }
     };
 

+ 6 - 2
desktop/core/src/desktop/js/apps/notebook2/editorViewModel.js

@@ -28,7 +28,11 @@ import {
   ACTIVE_SNIPPET_CONNECTOR_CHANGED_EVENT,
   GET_ACTIVE_SNIPPET_CONNECTOR_EVENT
 } from 'apps/notebook2/events';
-import { CONFIG_REFRESHED_EVENT, GET_KNOWN_CONFIG_EVENT, findConnector } from 'utils/hueConfig';
+import {
+  CONFIG_REFRESHED_EVENT,
+  GET_KNOWN_CONFIG_EVENT,
+  findEditorConnector
+} from 'utils/hueConfig';
 
 class EditorViewModel {
   constructor(editorId, notebooks, options, CoordinatorEditorViewModel, RunningCoordinatorModel) {
@@ -312,7 +316,7 @@ class EditorViewModel {
   }
 
   async newNotebook(editorType, callback, queryTab) {
-    const connector = findConnector(connector => connector.type === editorType);
+    const connector = findEditorConnector(connector => connector.type === editorType);
     if (!connector) {
       console.warn('No connector found for type ' + editorType);
     } else {

+ 4 - 2
desktop/core/src/desktop/js/apps/notebook2/notebook.test.js

@@ -51,7 +51,7 @@ describe('notebook.js', () => {
   it('should serialize a notebook to JSON', async () => {
     const connectors = [{ type: 'hive', dialect: 'hive' }, { type: 'impala', dialect: 'impala' }];
     const spy = jest
-      .spyOn(hueConfig, 'findConnector')
+      .spyOn(hueConfig, 'findEditorConnector')
       .mockImplementation(connectors.find.bind(connectors));
 
     const notebook = new Notebook(viewModel, {});
@@ -74,7 +74,9 @@ describe('notebook.js', () => {
   it('should serialize a notebook context to JSON', async () => {
     const notebook = new Notebook(viewModel, {});
     const connectors = [{ type: 'hive', dialect: 'hive' }, { type: 'impala', dialect: 'impala' }];
-    jest.spyOn(hueConfig, 'findConnector').mockImplementation(connectors.find.bind(connectors));
+    jest
+      .spyOn(hueConfig, 'findEditorConnector')
+      .mockImplementation(connectors.find.bind(connectors));
 
     notebook.addSnippet({ connector: { dialect: 'hive' } });
 

+ 6 - 4
desktop/core/src/desktop/js/apps/notebook2/snippet.js

@@ -46,7 +46,7 @@ import {
 } from 'ko/bindings/ace/aceLocationHandler';
 import { EXECUTE_ACTIVE_EXECUTABLE_EVENT } from 'apps/notebook2/components/ko.executableActions';
 import { UPDATE_HISTORY_EVENT } from 'apps/notebook2/components/ko.queryHistory';
-import { findConnector, getLastKnownConfig } from 'utils/hueConfig';
+import { findEditorConnector, getLastKnownConfig } from 'utils/hueConfig';
 import { cancelActiveRequest } from 'api/apiUtils';
 import { getOptimizer } from 'catalog/optimizer/optimizer';
 import {
@@ -1039,7 +1039,7 @@ export default class Snippet {
   }
 
   changeDialect(dialect) {
-    const connector = findConnector(connector => connector.dialect === dialect);
+    const connector = findEditorConnector(connector => connector.dialect === dialect);
     if (!connector) {
       throw new Error('No connector found for dialect ' + dialect);
     }
@@ -1318,13 +1318,15 @@ export default class Snippet {
   initializeConnector(snippetRaw) {
     const connectorTypeToFind =
       (snippetRaw.connector && snippetRaw.connector.type) || snippetRaw.type;
-    let foundConnector = findConnector(connector => connector.type === connectorTypeToFind);
+    let foundConnector = findEditorConnector(connector => connector.type === connectorTypeToFind);
 
     if (!foundConnector) {
       // If not found by type pick the first by dialect
       const connectorDialectToFind =
         (snippetRaw.connector && snippetRaw.connector.dialect) || snippetRaw.type;
-      foundConnector = findConnector(connector => connector.dialect === connectorDialectToFind);
+      foundConnector = findEditorConnector(
+        connector => connector.dialect === connectorDialectToFind
+      );
     }
 
     if (!foundConnector && snippetRaw.connector) {

+ 1 - 1
desktop/core/src/desktop/js/apps/notebook2/snippet.test.js

@@ -51,7 +51,7 @@ describe('snippet.js', () => {
   it('should serialize a snippet context to JSON', async () => {
     const connectors = [{ type: 'hive', dialect: 'hive' }];
     const spy = jest
-      .spyOn(hueConfig, 'findConnector')
+      .spyOn(hueConfig, 'findEditorConnector')
       .mockImplementation(connectors.find.bind(connectors));
 
     const notebook = new Notebook(viewModel, {});

+ 2 - 2
desktop/core/src/desktop/js/apps/table_browser/metastoreSource.js

@@ -26,7 +26,7 @@ import {
   ASSIST_IS_DB_PANEL_READY_EVENT,
   ASSIST_SET_DATABASE_EVENT
 } from 'ko/components/assist/events';
-import { findConnector } from 'utils/hueConfig';
+import { findEditorConnector } from 'utils/hueConfig';
 
 class MetastoreSource {
   constructor(options) {
@@ -59,7 +59,7 @@ class MetastoreSource {
       }
     };
 
-    this.connector = ko.observable(findConnector(connector => connector.type === this.type));
+    this.connector = ko.observable(findEditorConnector(connector => connector.type === this.type));
 
     huePubSub.subscribe(ASSIST_DB_PANEL_IS_READY_EVENT, () => {
       this.lastLoadNamespacesDeferred.done(() => {

+ 34 - 37
desktop/core/src/desktop/js/ko/components/assist/assistStorageEntry.js

@@ -19,7 +19,7 @@ import * as ko from 'knockout';
 
 import apiHelper from 'api/apiHelper';
 import huePubSub from 'utils/huePubSub';
-import { GET_KNOWN_CONFIG_EVENT } from 'utils/hueConfig';
+import { findBrowserConnector, GET_KNOWN_CONFIG_EVENT, getRootFilePath } from 'utils/hueConfig';
 
 const PAGE_SIZE = 100;
 
@@ -187,6 +187,11 @@ class AssistStorageEntry {
       return;
     }
 
+    if (this.rootPath) {
+      const relativeFolders = folders.join('/').replace(new RegExp('^' + this.rootPath, ''), '');
+      folders = relativeFolders.split('/');
+    }
+
     const nextName = folders.shift();
     let loadedPages = 0;
 
@@ -349,46 +354,38 @@ class AssistStorageEntry {
     type = type.replace(/adl.*/i, 'adls');
     type = type.replace(/abfs.*/i, 'abfs');
 
-    huePubSub.publish(GET_KNOWN_CONFIG_EVENT, config => {
-      if (config && config.app_config && config.app_config.browser) {
-        const source = config.app_config.browser.interpreters.find(
-          interpreter => interpreter.type === type
+    const connector = findBrowserConnector(connector => connector.type === type);
+    if (connector) {
+      const rootPath = getRootFilePath(connector);
+      const rootEntry = new AssistStorageEntry({
+        source: connector,
+        rootPath: rootPath,
+        originalType: typeMatch && typeMatch[1],
+        definition: {
+          name: rootPath,
+          type: 'dir'
+        },
+        parent: null,
+        apiHelper: apiHelper
+      });
+
+      if (type === 'abfs' || type === 'adls') {
+        // ABFS / ADLS can have domain name in path. To prevent regression with s3 which allow periods in bucket name handle separately.
+        const azureMatch = path.match(
+          /^([^:]+):\/(\/((\w+)@)?[\w]+([\-\.]{1}\w+)*\.[\w]*)?(\/.*)?\/?/i
         );
-        if (source) {
-          const rootEntry = new AssistStorageEntry({
-            source: source,
-            originalType: typeMatch && typeMatch[1],
-            definition: {
-              name: '/',
-              type: 'dir'
-            },
-            parent: null,
-            apiHelper: apiHelper
-          });
-
-          if (type === 'abfs' || type === 'adls') {
-            // ABFS / ADLS can have domain name in path. To prevent regression with s3 which allow periods in bucket name handle separately.
-            const azureMatch = path.match(
-              /^([^:]+):\/(\/((\w+)@)?[\w]+([\-\.]{1}\w+)*\.[\w]*)?(\/.*)?\/?/i
-            );
-            path = (azureMatch ? azureMatch[6] || '' : path)
-              .replace(/(?:^\/)|(?:\/$)/g, '')
-              .split('/');
-            if (azureMatch && azureMatch[4]) {
-              path.unshift(azureMatch[4]);
-            }
-          } else {
-            path = (typeMatch ? typeMatch[2] : path).replace(/(?:^\/)|(?:\/$)/g, '').split('/');
-          }
-
-          rootEntry.loadDeep(path, deferred.resolve);
-        } else {
-          deferred.reject();
+        path = (azureMatch ? azureMatch[6] || '' : path).replace(/(?:^\/)|(?:\/$)/g, '').split('/');
+        if (azureMatch && azureMatch[4]) {
+          path.unshift(azureMatch[4]);
         }
       } else {
-        deferred.reject();
+        path = (typeMatch ? typeMatch[2] : path).replace(/(?:^\/)|(?:\/$)/g, '').split('/');
       }
-    });
+
+      rootEntry.loadDeep(path, deferred.resolve);
+    } else {
+      deferred.reject();
+    }
 
     return deferred.promise();
   }

+ 5 - 14
desktop/core/src/desktop/js/ko/components/assist/assistStorageEntry.test.js

@@ -17,8 +17,7 @@
 import $ from 'jquery';
 
 import AssistStorageEntry from './assistStorageEntry';
-import huePubSub from 'utils/huePubSub';
-import { GET_KNOWN_CONFIG_EVENT } from 'utils/hueConfig';
+import * as hueConfig from 'utils/hueConfig';
 
 describe('assistStorageEntry.js', () => {
   it('it should handle domain in ADLS/ABFS', () => {
@@ -59,17 +58,9 @@ describe('assistStorageEntry.js', () => {
       return deferred.promise();
     });
 
-    const pubSpy = jest.spyOn(huePubSub, 'publish').mockImplementation((topic, cb) => {
-      if (topic === GET_KNOWN_CONFIG_EVENT && cb) {
-        cb({
-          app_config: {
-            browser: {
-              interpreters: [{ type: 'abfs' }]
-            }
-          }
-        });
-      }
-    });
+    const findSpy = jest
+      .spyOn(hueConfig, 'findBrowserConnector')
+      .mockImplementation(() => ({ type: 'abfs', page: '' }));
 
     AssistStorageEntry.getEntry('abfs://test.com/path').always(entry => {
       expect(entry.path).toBe('/path');
@@ -84,7 +75,7 @@ describe('assistStorageEntry.js', () => {
       expect(entry.path).toBe('/path/p2');
     });
     expect(spy).toHaveBeenCalled();
-    expect(pubSpy).toHaveBeenCalled();
+    expect(findSpy).toHaveBeenCalled();
 
     spy.mockRestore();
     spy.mockClear();

+ 2 - 2
desktop/core/src/desktop/js/ko/components/assist/ko.assistDbPanel.js

@@ -23,7 +23,7 @@ import componentUtils from 'ko/components/componentUtils';
 import dataCatalog from 'catalog/dataCatalog';
 import huePubSub from 'utils/huePubSub';
 import I18n from 'utils/i18n';
-import { CONFIG_REFRESHED_EVENT, filterConnectors } from 'utils/hueConfig';
+import { CONFIG_REFRESHED_EVENT, filterEditorConnectors } from 'utils/hueConfig';
 import {
   ASSIST_DB_HIGHLIGHT_EVENT,
   ASSIST_DB_PANEL_IS_READY_EVENT,
@@ -836,7 +836,7 @@ class AssistDbPanel {
 
     const updateFromConfig = () => {
       const sources = [];
-      const connectors = filterConnectors(connector => connector.is_sql);
+      const connectors = filterEditorConnectors(connector => connector.is_sql);
       connectors.forEach(connector => {
         const source =
           this.sourceIndex[connector.type] ||

+ 2 - 2
desktop/core/src/desktop/js/ko/components/assist/ko.assistFunctionsPanel.js

@@ -21,7 +21,7 @@ import componentUtils from 'ko/components/componentUtils';
 import huePubSub from 'utils/huePubSub';
 import { PigFunctions, SqlFunctions } from 'sql/sqlFunctions';
 import I18n from 'utils/i18n';
-import { CONFIG_REFRESHED_EVENT, filterConnectors } from 'utils/hueConfig';
+import { CONFIG_REFRESHED_EVENT, filterEditorConnectors } from 'utils/hueConfig';
 
 export const NAME = 'assist-functions-panel';
 // prettier-ignore
@@ -184,7 +184,7 @@ class AssistFunctionsPanel {
 
       const uniqueDialects = {};
 
-      const configuredDialects = filterConnectors(connector => {
+      const configuredDialects = filterEditorConnectors(connector => {
         const isMatch =
           !uniqueDialects[connector.dialect] &&
           (connector.dialect === 'hive' ||

+ 2 - 2
desktop/core/src/desktop/js/ko/components/assist/ko.assistLangRefPanel.js

@@ -20,7 +20,7 @@ import * as ko from 'knockout';
 import componentUtils from 'ko/components/componentUtils';
 import huePubSub from 'utils/huePubSub';
 import I18n from 'utils/i18n';
-import { CONFIG_REFRESHED_EVENT, filterConnectors } from 'utils/hueConfig';
+import { CONFIG_REFRESHED_EVENT, filterEditorConnectors } from 'utils/hueConfig';
 import { simpleGet } from 'api/apiUtils';
 import { ASSIST_LANG_REF_PANEL_SHOW_TOPIC_EVENT } from './events';
 
@@ -158,7 +158,7 @@ class AssistLangRefPanel {
     const configUpdated = () => {
       const lastActiveDialect = this.activeDialect();
 
-      const configuredDialects = filterConnectors(
+      const configuredDialects = filterEditorConnectors(
         connector => connector.dialect === 'hive' || connector.dialect === 'impala'
       ).map(connector => connector.dialect);
       configuredDialects.sort();

+ 3 - 17
desktop/core/src/desktop/js/ko/components/assist/ko.assistStoragePanel.js

@@ -21,6 +21,7 @@ import AssistStorageEntry from './assistStorageEntry';
 import componentUtils from 'ko/components/componentUtils';
 import huePubSub from 'utils/huePubSub';
 import I18n from 'utils/i18n';
+import { getRootFilePath } from 'utils/hueConfig';
 
 // prettier-ignore
 const TEMPLATE = `
@@ -174,21 +175,6 @@ const TEMPLATE = `
   <!-- /ko -->
 `;
 
-const rootPathRegex = /.*%3A%2F%2F(.+)$/;
-
-/**
- * This takes the initial path from the "browser" config, used in cases where the users can't access '/'
- */
-const getRootPath = source => {
-  if (source) {
-    const match = source.page.match(rootPathRegex);
-    if (match) {
-      return match[1] + '/';
-    }
-  }
-  return '';
-};
-
 class AssistStoragePanel {
   /**
    * @param {Object} options
@@ -209,13 +195,13 @@ class AssistStoragePanel {
     this.activeSource = ko.observable(foundLastSource);
     this.loading = ko.observable();
     this.initialized = false;
-    this.rootPath = getRootPath(this.activeSource());
+    this.rootPath = getRootFilePath(this.activeSource());
 
     this.selectedStorageEntry = ko.observable();
 
     this.activeSource.subscribe(newValue => {
       if (newValue) {
-        this.rootPath = getRootPath(this.activeSource());
+        this.rootPath = getRootFilePath(this.activeSource());
         apiHelper.setInTotalStorage('assist', 'lastStorageSource', newValue.type);
         this.selectedStorageEntry(undefined);
         this.reload();

+ 2 - 2
desktop/core/src/desktop/js/ko/components/contextPopover/ko.quickQueryContext.js

@@ -27,7 +27,7 @@ import DisposableComponent from 'ko/components/DisposableComponent';
 import Executor from 'apps/notebook2/execution/executor';
 import SqlExecutable from 'apps/notebook2/execution/sqlExecutable';
 import sqlStatementsParser from 'parse/sqlStatementsParser';
-import { CONFIG_REFRESHED_EVENT, filterConnectors } from 'utils/hueConfig';
+import { CONFIG_REFRESHED_EVENT, filterEditorConnectors } from 'utils/hueConfig';
 
 export const NAME = 'quick-query-context';
 
@@ -161,7 +161,7 @@ class QuickQueryContext extends DisposableComponent {
   }
 
   updateFromConfig() {
-    const configuredSqlConnectors = filterConnectors(connector => connector.is_sql);
+    const configuredSqlConnectors = filterEditorConnectors(connector => connector.is_sql);
     this.availableInterpreters(configuredSqlConnectors);
 
     const found =

+ 1 - 1
desktop/core/src/desktop/js/ko/components/contextPopover/ko.quickQueryContext.test.js

@@ -26,7 +26,7 @@ describe('ko.quickQueryContext.js', () => {
 
   it('should render component', async () => {
     const connectorsSpy = jest
-      .spyOn(hueConfig, 'filterConnectors')
+      .spyOn(hueConfig, 'filterEditorConnectors')
       .mockImplementation(() => [{ type: 'impala' }]);
     const computeSpy = jest
       .spyOn(apiHelper, 'fetchContextComputes')

+ 2 - 2
desktop/core/src/desktop/js/topNavViewModel.js

@@ -20,7 +20,7 @@ import apiHelper from 'api/apiHelper';
 import huePubSub from 'utils/huePubSub';
 import {
   CONFIG_REFRESHED_EVENT,
-  findConnector,
+  findEditorConnector,
   GET_KNOWN_CONFIG_EVENT,
   REFRESH_CONFIG_EVENT
 } from 'utils/hueConfig';
@@ -45,7 +45,7 @@ class TopNavViewModel {
 
       self.hasJobBrowser(
         window.HAS_JOB_BROWSER &&
-          findConnector(
+          findEditorConnector(
             connector =>
               connector.dialect === 'yarn' ||
               connector.dialect === 'impala' ||

+ 42 - 10
desktop/core/src/desktop/js/utils/hueConfig.js

@@ -52,14 +52,14 @@ export const refreshConfig = async () => {
   return lastConfigPromise;
 };
 
-const validConnectorConfig = config => {
+const validConnectorConfig = (config, type) => {
   if (
     !config ||
     !config.app_config ||
-    !config.app_config.editor ||
-    !config.app_config.editor.interpreters
+    !config.app_config[type] ||
+    !config.app_config[type].interpreters
   ) {
-    console.error('No "interpreters" attribute present in the config.');
+    console.error(`No "interpreters" attribute present in the config for type "${type}".`);
     return false;
   }
   return true;
@@ -67,21 +67,53 @@ const validConnectorConfig = config => {
 
 export const getLastKnownConfig = () => lastKnownConfig;
 
-export const findConnector = connectorTest => {
-  if (validConnectorConfig(lastKnownConfig)) {
-    const connectors = lastKnownConfig.app_config.editor.interpreters;
+const CONNECTOR_TYPES = {
+  editor: 'editor',
+  browser: 'browser'
+};
+
+const findConnector = (connectorTest, type) => {
+  if (validConnectorConfig(lastKnownConfig, type)) {
+    const connectors = lastKnownConfig.app_config[type].interpreters;
     return connectors.find(connectorTest);
   }
 };
 
-export const filterConnectors = connectorTest => {
-  if (validConnectorConfig(lastKnownConfig)) {
-    const connectors = lastKnownConfig.app_config.editor.interpreters;
+const filterConnectors = (connectorTest, type) => {
+  if (validConnectorConfig(lastKnownConfig, type)) {
+    const connectors = lastKnownConfig.app_config[type].interpreters;
     return connectors.filter(connectorTest);
   }
   return [];
 };
 
+export const findBrowserConnector = connectorTest =>
+  findConnector(connectorTest, CONNECTOR_TYPES.browser);
+
+export const findEditorConnector = connectorTest =>
+  findConnector(connectorTest, CONNECTOR_TYPES.editor);
+
+export const filterEditorConnectors = connectorTest =>
+  filterConnectors(connectorTest, CONNECTOR_TYPES.editor);
+
+const rootPathRegex = /.*%3A%2F%2F(.+)$/;
+
+/**
+ * This takes the initial path from the "browser" config, used in cases where the users can't access '/'
+ * for abfs etc.
+ */
+export const getRootFilePath = connector => {
+  if (!connector || connector.type === 'hdfs') {
+    return '';
+  }
+  const match = connector.page.match(rootPathRegex);
+  if (match) {
+    return match[1] + '/';
+  }
+
+  return '';
+};
+
 huePubSub.subscribe(REFRESH_CONFIG_EVENT, refreshConfig);
 
 // TODO: Replace GET_KNOWN_CONFIG_EVENT pubSub with sync getKnownConfig const