Browse Source

HUE-5270 [assist] Context popover should handle complex types

Johan Ahlen 9 năm trước cách đây
mục cha
commit
f1dafa89f1

+ 92 - 18
desktop/core/src/desktop/static/desktop/js/apiHelper.js

@@ -1091,21 +1091,94 @@ var ApiHelper = (function () {
     return typeof self.lastKnownDatabases[sourceType] !== 'undefined' && self.lastKnownDatabases[sourceType].indexOf(databaseName) > -1;
   };
 
+  ApiHelper.prototype.expandComplexIdentifierChain = function (sourceType, database, identifierChain, successCallback, errorCallback) {
+    var self = this;
+
+    var fetchFieldsInternal =  function (table, database, identifierChain, callback, errorCallback, fetchedFields) {
+      if (!identifierChain) {
+        identifierChain = [];
+      }
+      if (identifierChain.length > 0) {
+        fetchedFields.push(identifierChain[0].name);
+        identifierChain = identifierChain.slice(1);
+      }
+
+      // Parser sometimes knows if it's a map or array.
+      if (identifierChain.length > 0 && (identifierChain[0].name === 'item' || identifierChain[0].name === 'value')) {
+        fetchedFields.push(identifierChain[0].name);
+        identifierChain = identifierChain.slice(1);
+      }
+
+
+      self.fetchFields({
+        sourceType: sourceType,
+        databaseName: database,
+        tableName: table,
+        fields: fetchedFields,
+        timeout: self.timeout,
+        successCallback: function (data) {
+          if (sourceType === 'hive'
+              && typeof data.extended_columns !== 'undefined'
+              && data.extended_columns.length === 1
+              && data.extended_columns.length
+              && /^map|array|struct/i.test(data.extended_columns[0].type)) {
+            identifierChain.unshift({ name: data.extended_columns[0].name })
+          }
+          if (identifierChain.length > 0) {
+            if (typeof identifierChain[0].name !== 'undefined' && /value|item|key/i.test(identifierChain[0].name)) {
+              fetchedFields.push(identifierChain[0].name);
+              identifierChain.shift();
+            } else {
+              if (data.type === 'array') {
+                fetchedFields.push('item')
+              }
+              if (data.type === 'map') {
+                fetchedFields.push('value')
+              }
+            }
+            fetchFieldsInternal(table, database, identifierChain, callback, errorCallback, fetchedFields)
+          } else {
+            fetchedFields.unshift(table);
+            callback(fetchedFields);
+          }
+        },
+        silenceErrors: true,
+        errorCallback: errorCallback
+      });
+    };
+
+    fetchFieldsInternal(identifierChain.shift().name, database, identifierChain, successCallback, errorCallback, []);
+  };
+
   /**
    *
    * @param {Object} options
    * @param {string} options.sourceType
+   * @param {function} options.errorCallback
    * @param {Object[]} options.identifierChain
    * @param {string} options.identifierChain.name
    * @param {string} options.defaultDatabase
+   *
+   * @param {function} successCallback
    */
-  ApiHelper.prototype.identifierChainToPath = function (options) {
+  ApiHelper.prototype.identifierChainToPath = function (options, successCallback) {
     var self = this;
+    var identifierChainClone = options.identifierChain.concat();
     var path = [];
-    if (! self.containsDatabase(options.sourceType, options.identifierChain[0].name)) {
+    if (! self.containsDatabase(options.sourceType, identifierChainClone[0].name)) {
       path.push(options.defaultDatabase);
+    } else {
+      path.push(identifierChainClone.shift().name)
+    }
+
+    if (identifierChainClone.length > 1) {
+      self.expandComplexIdentifierChain(options.sourceType, path[0], identifierChainClone, function (fetchedFields) {
+        successCallback(path.concat(fetchedFields))
+      }, options.errorCallback);
+    } else {
+      successCallback(path.concat($.map(identifierChainClone, function (identifier) { return identifier.name })))
     }
-    return path.concat($.map(options.identifierChain, function (identifier) { return identifier.name }));
+
   };
 
   /**
@@ -1123,13 +1196,13 @@ var ApiHelper = (function () {
    */
   ApiHelper.prototype.fetchAutocomplete = function (options) {
     var self = this;
-    var path = self.identifierChainToPath(options);
-
-    fetchAssistData.bind(self)($.extend({}, options, {
-      url: AUTOCOMPLETE_API_PREFIX + path.join('/'),
-      errorCallback: self.assistErrorCallback(options),
-      cacheCondition: fieldCacheCondition
-    }));
+    self.identifierChainToPath(options, function (path) {
+      fetchAssistData.bind(self)($.extend({}, options, {
+        url: AUTOCOMPLETE_API_PREFIX + path.join('/'),
+        errorCallback: self.assistErrorCallback(options),
+        cacheCondition: fieldCacheCondition
+      }));
+    });
   };
 
   /**
@@ -1147,14 +1220,15 @@ var ApiHelper = (function () {
    */
   ApiHelper.prototype.fetchSamples = function (options) {
     var self = this;
-    var path = self.identifierChainToPath(options);
-    fetchAssistData.bind(self)($.extend({}, options, {
-      url: SAMPLE_API_PREFIX + path.join('/'),
-      errorCallback: self.assistErrorCallback(options),
-      cacheCondition: function (data) {
-        return data.status === 0 && typeof data.rows !== 'undefined' && data.rows.length > 0;
-      }
-    }));
+    self.identifierChainToPath(options, function (path) {
+      fetchAssistData.bind(self)($.extend({}, options, {
+        url: SAMPLE_API_PREFIX + path.join('/'),
+        errorCallback: self.assistErrorCallback(options),
+        cacheCondition: function (data) {
+          return data.status === 0 && typeof data.rows !== 'undefined' && data.rows.length > 0;
+        }
+      }));
+    });
   };
 
 

+ 10 - 6
desktop/core/src/desktop/static/desktop/js/assist/assistDbEntry.js

@@ -158,8 +158,8 @@ var AssistDbEntry = (function () {
     self.statsVisible(true);
     huePubSub.publish('sql.context.popover.show', {
       data: {
-        type: self.definition.isColumn ? 'column' : 'table',
-        identifierChain: self.definition.isColumn ? [{ name: self.tableName }, { name: self.columnName }] : [{ name: self.tableName }]
+        type: self.definition.isColumn ? 'column' : (self.definition.isComplex ? 'complex' : (self.definition.isTable ? 'table' : 'database')),
+        identifierChain: $.map(self.getHierarchy(), function (name) { return { name: name }})
       },
       orientation: 'right',
       sourceType: self.sourceType,
@@ -284,14 +284,16 @@ var AssistDbEntry = (function () {
               name: "key",
               displayName: "key (" + data.key.type + ")",
               title: "key (" + data.key.type + ")",
-              type: data.key.type
+              type: data.key.type,
+              isComplex: true
             }),
             self.createEntry({
               name: "value",
               displayName: "value (" + data.value.type + ")",
               title: "value (" + data.value.type + ")",
               isMapValue: true,
-              type: data.value.type
+              type: data.value.type,
+              isComplex: true
             })
           ];
         } else if (data.type == "struct") {
@@ -300,7 +302,8 @@ var AssistDbEntry = (function () {
               name: field.name,
               displayName: field.name + " (" + field.type + ")",
               title: field.name + " (" + field.type + ")",
-              type: field.type
+              type: field.type,
+              isComplex: true
             });
           });
         } else if (data.type == "array") {
@@ -310,7 +313,8 @@ var AssistDbEntry = (function () {
               displayName: "item (" + data.item.type + ")",
               title: "item (" + data.item.type + ")",
               isArray: true,
-              type: data.item.type
+              type: data.item.type,
+              isComplex: true
             })
           ];
         }

+ 13 - 5
desktop/core/src/desktop/static/desktop/js/ko.hue-bindings.js

@@ -375,11 +375,15 @@
       var selector = options.selector;
       var showTimeout = -1;
       var hideTimeout = -1;
-      ko.utils.domData.set(element, 'visibleOnHover.override', ko.utils.unwrapObservable(options.override) || false)
+      ko.utils.domData.set(element, 'visibleOnHover.override', ko.utils.unwrapObservable(options.override) || false);
       var inside = false;
 
       var show = function () {
-        $element.find(selector).fadeTo("fast", 1);
+        if (options.childrenOnly) {
+          $element.children(selector).fadeTo("fast", 1);
+        } else {
+          $element.find(selector).fadeTo("fast", 1);
+        }
         window.clearTimeout(hideTimeout);
       };
 
@@ -387,13 +391,17 @@
         if (! inside) {
           window.clearTimeout(showTimeout);
           hideTimeout = window.setTimeout(function () {
-            $element.find(selector).fadeTo("fast", 0);
+            if (options.childrenOnly) {
+              $element.children(selector).fadeTo("fast", 0);
+            } else {
+              $element.find(selector).fadeTo("fast", 0);
+            }
           }, 10);
         }
       };
 
-      ko.utils.domData.set(element, 'visibleOnHover.show', show)
-      ko.utils.domData.set(element, 'visibleOnHover.hide', hide)
+      ko.utils.domData.set(element, 'visibleOnHover.show', show);
+      ko.utils.domData.set(element, 'visibleOnHover.hide', hide);
 
       if (ko.utils.domData.get(element, 'visibleOnHover.override')) {
         window.setTimeout(show, 1);

+ 1 - 1
desktop/core/src/desktop/templates/assist.mako

@@ -543,7 +543,7 @@ from metadata.conf import has_navigator
   </script>
 
   <script type="text/html" id="assist-column-entry">
-    <li data-bind="templateContextMenu: { template: 'sql-context-items', scrollContainer: '.assist-db-scrollable' }, visible: ! hasErrors(), visibleOnHover: { override: statsVisible, selector: definition.isView ? '.table-actions' : '.column-actions' }, css: { 'assist-table': definition.isView, 'assist-column': definition.isColumn }">
+    <li data-bind="templateContextMenu: { template: 'sql-context-items', scrollContainer: '.assist-db-scrollable' }, visible: ! hasErrors(), visibleOnHover: { childrenOnly: true, override: statsVisible, selector: definition.isView ? '.table-actions' : '.column-actions' }, css: { 'assist-table': definition.isView, 'assist-column': definition.isColumn || definition.isComplex }">
       <div class="assist-actions column-actions" style="opacity: 0">
         <a class="inactive-action" href="javascript:void(0)" data-bind="visible: navigationSettings.showStats, click: showContextPopover, css: { 'blue': statsVisible }"><i class="fa fa-fw fa-info" title="${_('Show details')}"></i></a>
       </div>

+ 84 - 28
desktop/core/src/desktop/templates/sql_context_popover.mako

@@ -348,6 +348,15 @@ from metadata.conf import has_navigator
     </div>
   </script>
 
+  <script type="text/html" id="sql-context-complex-details">
+    <div class="sql-context-flex-fill" data-bind="with: fetchedData, nicescroll">
+      <div style="margin: 15px;">
+        <a class="pointer" data-bind="visible: typeof sample !== 'undefined', text: name || $parents[2].title, attr: { title: name || $parents[2].title }, click: function() { huePubSub.publish('sql.context.popover.scroll.to.column', name || $parents[2].title); }"></a>
+        <span data-bind="visible: typeof sample === 'undefined', text: name || $parents[2].title, attr: { title: name || $parents[2].title }"></span> (<span data-bind="text: type.indexOf('<') !== -1 ? type.substring(0, type.indexOf('<')) : type, attr: { title: type }"></span>)
+      </div>
+    </div>
+  </script>
+
   <script type="text/html" id="sql-context-table-and-column-sample">
     <div class="sql-context-flex-fill" data-bind="with: fetchedData">
       <div class="context-sample sample-scroll" style="text-align: left; padding: 3px; overflow: hidden; height: 100%">
@@ -622,7 +631,7 @@ from metadata.conf import has_navigator
         });
       };
 
-      function TableAndColumnContextTabs(data, sourceType, defaultDatabase, isColumn) {
+      function TableAndColumnContextTabs(data, sourceType, defaultDatabase, isColumn, isComplex) {
         var self = this;
         self.tabs = ko.observableArray();
 
@@ -635,11 +644,42 @@ from metadata.conf import has_navigator
         self.analysis = new GenericTabContents(data.identifierChain, sourceType, defaultDatabase, apiHelper.fetchAnalysis);
         self.partitions = new GenericTabContents(data.identifierChain, sourceType, defaultDatabase, apiHelper.fetchPartitions);
 
+        self.title = data.identifierChain[data.identifierChain.length - 1].name;
+
         self.activeTab = ko.observable();
 
         self.activeTab.subscribe(function (newValue) {
-          if (newValue === 'sample' && typeof self.sample.fetchedData() === 'undefined') {
-            self.sample.fetch(self.initializeSamplesTable);
+          if (newValue === 'sample') {
+            if (typeof self.sample.fetchedData() === 'undefined') {
+              if (!isComplex) {
+                self.sample.fetch(self.initializeSamplesTable);
+              } else {
+                var data = self.columnDetails.fetchedData();
+                var rows = [];
+                data.sample.forEach(function (sample) {
+                  rows.push([sample]);
+                });
+                self.sample.fetchedData({
+                  headers: [ data.name || self.title ],
+                  rows: rows
+                });
+                self.initializeSamplesTable(self.sample.fetchedData());
+              }
+            }
+          } else if (newValue === 'complexDetails') {
+            if (typeof self.columnDetails.fetchedData() === 'undefined') {
+              self.columnDetails.fetch(function (data) {
+                if (data.sample) {
+                  self.tabs.push({
+                    id: 'sample',
+                    label: '${ _("Sample") }',
+                    template: 'sql-context-table-and-column-sample',
+                    templateData: self.sample,
+                    errorText: '${ _("There was a problem loading the samples.") }'
+                  });
+                }
+              })
+            }
           } else if (typeof self[newValue].fetchedData() === 'undefined') {
             self[newValue].fetch();
           }
@@ -655,6 +695,16 @@ from metadata.conf import has_navigator
             isColumn: true
           });
           self.activeTab('columnDetails');
+        } else if (isComplex) {
+          self.tabs.push({
+            id: 'complexDetails',
+            label: '${ _("Details") }',
+            template: 'sql-context-complex-details',
+            templateData: self.columnDetails,
+            errorText: '${ _("There was a problem loading the details.") }',
+            isColumn: false
+          });
+          self.activeTab('complexDetails');
         } else {
           self.tabs.push({
             id: 'columns',
@@ -673,17 +723,18 @@ from metadata.conf import has_navigator
             isColumn: false
           });
           self.activeTab('columns');
-
         }
 
-        self.tabs.push({
-          id: 'sample',
-          label: '${ _("Sample") }',
-          template: 'sql-context-table-and-column-sample',
-          templateData: self.sample,
-          errorText: '${ _("There was a problem loading the samples.") }',
-          isColumn: isColumn
-        });
+        if (!isComplex) {
+          self.tabs.push({
+            id: 'sample',
+            label: '${ _("Sample") }',
+            template: 'sql-context-table-and-column-sample',
+            templateData: self.sample,
+            errorText: '${ _("There was a problem loading the samples.") }',
+            isColumn: isColumn
+          });
+        }
 
         if (isColumn) {
           self.columnDetails.fetch(function (data) {
@@ -696,7 +747,7 @@ from metadata.conf import has_navigator
               isColumn: true
             });
           });
-        } else {
+        } else if (!isComplex) {
           self.tableDetails.fetch(function (data) {
             if (data.partition_keys.length === 0) {
               self.tabs.push({
@@ -769,23 +820,23 @@ from metadata.conf import has_navigator
           }
         }));
 
-        var path = apiHelper.identifierChainToPath({
+        apiHelper.identifierChainToPath({
           sourceType: sourceType,
           defaultDatabase: defaultDatabase,
           identifierChain: data.identifierChain
-        });
-
-        pubSubs.push(huePubSub.subscribe('sql.context.popover.show.in.assist', function () {
-          huePubSub.publish('assist.db.highlight', {
-            sourceType: sourceType,
-            path: path
-          });
-          huePubSub.publish('sql.context.popover.hide')
-        }));
+        }, function (path) {
+          pubSubs.push(huePubSub.subscribe('sql.context.popover.show.in.assist', function () {
+            huePubSub.publish('assist.db.highlight', {
+              sourceType: sourceType,
+              path: path
+            });
+            huePubSub.publish('sql.context.popover.hide')
+          }));
 
-        pubSubs.push(huePubSub.subscribe('sql.context.popover.open.in.metastore', function () {
-          window.open('/metastore/table/' + path.join('/'), '_blank');
-        }));
+          pubSubs.push(huePubSub.subscribe('sql.context.popover.open.in.metastore', function () {
+            window.open('/metastore/table/' + path.join('/'), '_blank');
+          }));
+        });
       }
 
       TableAndColumnContextTabs.prototype.refetchSamples = function () {
@@ -1257,6 +1308,7 @@ from metadata.conf import has_navigator
         self.isDatabase = params.data.type === 'database';
         self.isTable = params.data.type === 'table';
         self.isColumn = params.data.type === 'column';
+        self.isComplex = params.data.type === 'complex';
         self.isFunction = params.data.type === 'function';
         self.isHdfs = params.data.type === 'hdfs';
         self.isAsterisk = params.data.type === 'asterisk';
@@ -1266,11 +1318,15 @@ from metadata.conf import has_navigator
           self.title = self.data.identifierChain[self.data.identifierChain.length - 1].name;
           self.iconClass = 'fa-database';
         } else if (self.isTable) {
-          self.contents = new TableAndColumnContextTabs(self.data, self.sourceType, self.defaultDatabase, false);
+          self.contents = new TableAndColumnContextTabs(self.data, self.sourceType, self.defaultDatabase, false, false);
           self.title = self.data.identifierChain[self.data.identifierChain.length - 1].name;
           self.iconClass = 'fa-table'
+        } else if (self.isComplex) {
+          self.contents = new TableAndColumnContextTabs(self.data, self.sourceType, self.defaultDatabase, false, true);
+          self.title = self.data.identifierChain[self.data.identifierChain.length - 1].name;
+          self.iconClass = 'fa-columns'
         } else if (self.isColumn) {
-          self.contents = new TableAndColumnContextTabs(self.data, self.sourceType, self.defaultDatabase, true);
+          self.contents = new TableAndColumnContextTabs(self.data, self.sourceType, self.defaultDatabase, true, false);
           self.title = self.data.identifierChain[self.data.identifierChain.length - 1].name;
           self.iconClass = 'fa-columns'
         } else if (self.isFunction) {