Эх сурвалжийг харах

HUE-8529 [frontend] Create a context selector component

This creates a re-usable component for compute, namespace and database selection.
Johan Ahlen 7 жил өмнө
parent
commit
85b796ad51

+ 2 - 0
desktop/core/src/desktop/templates/ko_components.mako

@@ -18,6 +18,7 @@
 <%namespace name="koBreadCrumbs" file="/ko_components/ko_breadcrumbs.mako" />
 <%namespace name="koCatalogEntriesTable" file="/ko_components/ko_catalog_entries_table.mako" />
 <%namespace name="koContextPopover" file="/ko_components/ko_context_popover.mako" />
+<%namespace name="koContextSelector" file="/ko_components/ko_context_selector.mako" />
 <%namespace name="koDropDown" file="/ko_components/ko_drop_down.mako" />
 <%namespace name="koFavoriteApp" file="/ko_components/ko_favorite_app.mako" />
 <%namespace name="koGlobalSearch" file="/ko_components/ko_global_search.mako" />
@@ -37,6 +38,7 @@
   ${ koBreadCrumbs.breadCrumbs() }
   ${ koCatalogEntriesTable.catalogEntriesTable() }
   ${ koContextPopover.contextPopover() }
+  ${ koContextSelector.contextSelector() }
   ${ koDropDown.dropDown() }
   ${ koFavoriteApp.favoriteApp() }
   ${ koGlobalSearch.globalSearch() }

+ 280 - 0
desktop/core/src/desktop/templates/ko_components/ko_context_selector.mako

@@ -0,0 +1,280 @@
+## 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 import conf
+from desktop.lib.i18n import smart_unicode
+
+from django.utils.translation import ugettext as _
+from desktop.views import _ko
+%>
+
+<%def name="contextSelector()">
+
+  <script type="text/html" id="hue-context-selector-template">
+    <!-- ko if: loadingContext -->
+    <i class="fa fa-spinner fa-spin muted"></i>
+    <!-- /ko -->
+
+    <div class="inline-block" style="display:none;" data-bind="visible: !loadingContext()">
+      <!-- ko if: window.HAS_MULTI_CLUSTER -->
+      <!-- ko if: availableComputes().length > 0 && !hideComputes -->
+      <span class="editor-header-title">${ _('Compute') }</span>
+      <div data-bind="component: { name: 'hue-drop-down', params: { value: compute, entries: availableComputes, labelAttribute: 'name', searchable: true, linkTitle: '${ _ko('Active compute') }' } }" style="display: inline-block"></div>
+      <!-- /ko -->
+      <!-- ko if: availableComputes().length === 0 && !hideComputes -->
+      <span class="editor-header-title"><i class="fa fa-warning"></i> ${ _('No computes found') }</span>
+      <!-- /ko -->
+
+      <!-- ko if: availableNamespaces().length > 0 && !hideNamespaces -->
+      <span class="editor-header-title">${ _('Namespace') }</span>
+      <div data-bind="component: { name: 'hue-drop-down', params: { value: namespace, entries: availableNamespaces, labelAttribute: 'name', searchable: true, linkTitle: '${ _ko('Active namespace') }' } }" style="display: inline-block"></div>
+      <!-- /ko -->
+      <!-- ko if: availableNamespaces().length === 0 && !hideNamespaces -->
+      <span class="editor-header-title"><i class="fa fa-warning"></i> ${ _('No namespaces found') }</span>
+      <!-- /ko -->
+      <!-- /ko -->
+
+      <!-- ko if: availableDatabases().length > 0 && !hideDatabases-->
+      <span class="editor-header-title">${ _('Database') }</span>
+      <div data-bind="component: { name: 'hue-drop-down', params: { value: database, entries: availableDatabases, foreachVisible: true, searchable: true, linkTitle: '${ _ko('Active database') }' } }" style="display: inline-block"></div>
+      <!-- /ko -->
+      <!-- ko if: availableDatabases().length === 0  && !hideDatabases -->
+      <span class="editor-header-title"><i class="fa fa-warning"></i> ${ _('No databases found') }</span>
+      <!-- /ko -->
+    </div>
+  </script>
+
+  <script type="text/javascript">
+    (function () {
+
+      /**
+       * This is a component for compute, namespace and database selection. All parameters are optional except the
+       * sourceType, if for instance no database and namespace observables are provided it will only show compute
+       * selection.
+       *
+       * If it's desired to just show namespaces for a given compute you can force hide the compute selection by
+       * setting hideComputes to true and the value of the compute observable will be used.
+       *
+       * Example:
+       *
+       *   <!-- ko component: {
+       *     name: 'hue-context-selector',
+       *     params: {
+       *       sourceType: 'impala',
+       *       compute: myComputeObservable,
+       *       namespace: myNamespaceObservable,
+       *     }
+       *   } --><!-- /ko -->
+       *
+       * @param {Object} params
+       * @param {ko.observable} params.sourceType
+       * @param {ko.observable} [params.compute]
+       * @param {ko.observable} [params.namespace]
+       * @param {ko.observable} [params.database]
+       * @param {ko.observableArray} [params.availableDatabases]
+       * @param {boolean} [params.hideComputes] - Can be used to force hide compute selection even if a compute
+       *                                          observable is provided.
+       * @param {boolean} [params.hideNamespaces] - Can be used to force hide namespace selection even if a namespace
+       *                                            observable is provided.
+       * @param {boolean} [params.hideDatabases] - Can be used to force hide database selection even if a database
+       *                                           observable is provided.
+       * @constructor
+       */
+      var HueContextSelector = function (params) {
+        var self = this;
+
+        self.sourceType = params.sourceType;
+
+        self.loadingComputes = ko.observable(false);
+        self.availableComputes = ko.observableArray();
+        self.compute = params.compute;
+        self.hideComputes = params.hideComputes || !self.compute;
+
+        if (params.compute) {
+          self.loadingComputes(true);
+          self.lastComputesPromise = ContextCatalog.getComputes({ sourceType: self.sourceType() }).done(function (computes) {
+            self.availableComputes(computes);
+            if (!self.compute() || !computes.some(function (compute) {
+              if (compute.id === self.compute().id) {
+                self.compute(compute);
+                return true;
+              }
+            })) {
+              self.compute(computes[0]);
+            }
+          }).always(function () {
+            self.loadingComputes(false);
+          });
+        } else {
+          self.lastComputesPromise = $.Deferred().resolve().promise();
+        }
+
+
+        self.loadingNamespaces = ko.observable(false);
+        self.availableNamespaces = ko.observableArray();
+        self.namespace = params.namespace;
+        self.hideNamespaces = params.hideNamespaces || !self.namespace;
+
+        self.lastNamespacePromise = undefined;
+        self.reloadNamespaces = function () {
+          if (params.namespace) {
+            self.loadingNamespaces(true);
+            self.lastNamespacePromise = ContextCatalog.getNamespaces({ sourceType: self.sourceType() }).done(function (context) {
+              self.availableNamespaces(context.namespaces);
+              if (!self.namespace() || !context.namespaces.some(function (namespace) {
+                if (namespace.id === self.namespace().id) {
+                  self.namespace(namespace);
+                  return true;
+                }})) {
+                self.namespace(context.namespaces[0]);
+              }
+            }).always(function () {
+              self.loadingNamespaces(false);
+            });
+          } else {
+            self.lastNamespacePromise = $.Deferred().resolve().promise();
+          }
+        };
+        self.reloadNamespaces();
+
+        self.loadingDatabases = ko.observable(false);
+        self.availableDatabases = params.availableDatabases || ko.observableArray();
+        self.database = params.database;
+        self.hideDatabases = params.hideDatabases || !self.database;
+
+        var updateDatabaseThrottle = -1;
+        self.updateDatabases = function () {
+          if (!self.hideDatabases) {
+            self.loadingDatabases(true);
+            $.when(self.lastNamespacePromise, self.lastComputesPromise).done(function () {
+              window.clearTimeout(updateDatabaseThrottle);
+              updateDatabaseThrottle = window.setTimeout(function () {
+                DataCatalog.getEntry({
+                  sourceType: self.sourceType(),
+                  namespace: self.namespace(),
+                  compute: self.compute(),
+                  path: [],
+                  definition: { type: 'source' }
+                }).done(function (sourceEntry) {
+                  sourceEntry.getChildren({ silenceErrors: true }).done(function (databaseEntries) {
+                    var databaseNames = [];
+                    databaseEntries.forEach(function (databaseEntry) {
+                      databaseNames.push(databaseEntry.name);
+                    });
+                    self.availableDatabases(databaseNames);
+                  }).fail(function () {
+                    self.availableDatabases([]);
+                  }).always(function () {
+                    if (self.database() && self.availableDatabases().indexOf(self.database()) === -1) {
+                      if (self.availableDatabases().length === 0 || self.availableDatabases().indexOf('default') !== -1) {
+                        self.database('default');
+                      } else {
+                        self.database(self.availableDatabases()[0])
+                      }
+                    }
+                    self.loadingDatabases(false);
+
+                    huePubSub.publish('assist.set.database', {
+                      source: self.sourceType(),
+                      namespace: self.namespace(),
+                      name: self.database()
+                    });
+                  });
+                });
+              }, 10);
+            });
+          } else {
+            self.availableDatabases([]);
+            self.database(undefined);
+          }
+        };
+
+        self.updateDatabases();
+
+        if (self.compute && self.namespace) {
+          $.when(self.lastNamespacePromise, self.lastComputesPromise).done(function () {
+            self.compute.subscribe(function (newCompute) {
+              // When the compute changes we set the corresponding namespace and update the databases
+              if (newCompute) {
+                var found = self.availableNamespaces().some(function (namespace) {
+                  // TODO: Remove name check once compute.namespace is a namespace ID
+                  if (namespace.name === newCompute.namespace || namespace.id === newCompute.namespace) {
+                    if (!self.namespace() || self.namespace().id !== namespace.id) {
+                      self.namespace(namespace);
+                      self.updateDatabases();
+                    }
+                    return true;
+                  }
+                });
+                if (!found && newCompute.namespace) {
+                  self.namespace(newCompute.namespace);
+                }
+              }
+            });
+            self.namespace.subscribe(function (newNamespace) {
+              if (newNamespace) {
+                // When the namespace changes we set the corresponding compute and update the databases
+                var found = self.availableComputes().some(function (compute) {
+                  if (compute.namespace === newNamespace.id) {
+                    if (!self.compute() || self.compute().id !== compute.id) {
+                      self.compute(compute);
+                      self.updateDatabases();
+                    }
+                    return true;
+                  }
+                });
+                if (!found) {
+                  self.compute(undefined);
+                }
+              }
+            })
+          });
+        } else {
+          self.updateDatabases();
+        }
+
+        if (self.database) {
+          huePubSub.subscribe('data.catalog.entry.refreshed', function (details) {
+            if (details.entry.isSource()) {
+              if (self.sourceType() === details.entry.getSourceType()) {
+                self.updateDatabases();
+              }
+            }
+          });
+        }
+
+        if (self.namespace) {
+          huePubSub.subscribe('context.catalog.namespaces.refreshed', function (sourceType) {
+            if (self.sourceType() === sourceType) {
+              self.reloadNamespaces();
+            }
+          });
+        }
+
+        self.loadingContext = ko.pureComputed(function () {
+          return self.loadingNamespaces() || self.loadingComputes() || self.loadingDatabases();
+        });
+      };
+
+      ko.components.register('hue-context-selector', {
+        viewModel: HueContextSelector,
+        template: { element: 'hue-context-selector-template' }
+      });
+    })();
+  </script>
+
+</%def>

+ 5 - 153
desktop/libs/notebook/src/notebook/static/notebook/js/notebook.ko.js

@@ -356,47 +356,11 @@ var EditorViewModel = (function() {
       return ApiHelper.getInstance(vm);
     };
 
-    self.loadingNamespaces = ko.observable(true);
-    self.availableNamespaces = ko.observableArray();
-    self.namespace = ko.observable();
-
-    self.lastNamespacePromise = undefined;
-    self.reloadNamespaces = function () {
-      self.loadingNamespaces(true);
-      self.lastNamespacePromise = ContextCatalog.getNamespaces({ sourceType: self.type() }).done(function (context) {
-        self.availableNamespaces(context.namespaces);
-        if (!snippet.namespace || !context.namespaces.some(function (namespace) {
-          if (namespace.id === snippet.namespace.id) {
-            self.namespace(namespace);
-            return true;
-          }})) {
-          self.namespace(context.namespaces[0]);
-        }
-      }).always(function () {
-        self.loadingNamespaces(false);
-      });
-    };
-    self.reloadNamespaces();
-
-    self.loadingComputes = ko.observable(true);
-    self.availableComputes = ko.observableArray();
-    self.compute = ko.observable();
+    self.namespace = ko.observable(snippet.namespace);
 
-    self.lastComputesPromise = ContextCatalog.getComputes({ sourceType: self.type() }).done(function (computes) {
-      self.availableComputes(computes);
-      if (!snippet.compute || !computes.some(function (compute) {
-        if (compute.id === snippet.compute.id) {
-          self.compute(compute);
-          return true;
-        }
-      })) {
-        self.compute(computes[0]);
-      }
-    }).always(function () {
-      self.loadingComputes(false);
-    });
+    self.compute = ko.observable(snippet.compute);
 
-    self.loadingDatabases = ko.observable(false);
+    self.availableDatabases = ko.observableArray();
     self.database = ko.observable();
     var previousDatabase = null;
 
@@ -409,98 +373,8 @@ var EditorViewModel = (function() {
         previousDatabase = newValue;
       }
     });
-    self.database(typeof snippet.database !== "undefined" && snippet.database != null ? snippet.database : null);
-    self.availableDatabases = ko.observableArray();
-
-    var updateDatabaseThrottle = -1;
-    self.updateDatabases = function () {
-      if (self.isSqlDialect()) {
-        self.loadingDatabases(true);
-        $.when(self.lastNamespacePromise, self.lastComputesPromise).done(function () {
-          window.clearTimeout(updateDatabaseThrottle);
-          updateDatabaseThrottle = window.setTimeout(function () {
-            DataCatalog.getEntry({
-              sourceType: self.type(),
-              namespace: self.namespace(),
-              compute: self.compute(),
-              path: [],
-              definition: {type: 'source'}
-            }).done(function (sourceEntry) {
-              sourceEntry.getChildren({silenceErrors: true}).done(function (databaseEntries) {
-                var databaseNames = [];
-                databaseEntries.forEach(function (databaseEntry) {
-                  databaseNames.push(databaseEntry.name);
-                });
-                self.availableDatabases(databaseNames);
-              }).fail(function () {
-                self.availableDatabases([]);
-              }).always(function () {
-                if (self.database() && self.availableDatabases().indexOf(self.database()) === -1) {
-                  if (self.availableDatabases().length === 0 || self.availableDatabases().indexOf('default') !== -1) {
-                    self.database('default');
-                  } else {
-                    self.database(self.availableDatabases()[0])
-                  }
-                }
-                self.loadingDatabases(false);
-
-                huePubSub.publish('assist.set.database', {
-                  source: self.type(),
-                  namespace: self.namespace(),
-                  name: self.database()
-                });
-              });
-            });
-          }, 10);
-        });
-      } else {
-        self.availableDatabases([]);
-        self.database(undefined);
-      }
-    };
-
-    self.loadingContext = ko.pureComputed(function () {
-      return self.loadingNamespaces() || self.loadingComputes() || self.loadingDatabases();
-    });
-
-    $.when(self.lastNamespacePromise, self.lastComputesPromise).done(function () {
-      self.compute.subscribe(function (newCompute) {
-        // When the compute changes we set the corresponding namespace and update the databases
-        if (newCompute) {
-          var found = self.availableNamespaces().some(function (namespace) {
-            // TODO: Remove name check once compute.namespace is a namespace ID
-            if (namespace.name === newCompute.namespace || namespace.id === newCompute.namespace) {
-              if (!self.namespace() || self.namespace().id !== namespace.id) {
-                self.namespace(namespace);
-                self.updateDatabases();
-              }
-              return true;
-            }
-          });
-          if (!found && newCompute.namespace) {
-            self.namespace(newCompute.namespace);
-          }
-        }
-      });
 
-      self.namespace.subscribe(function (newNamespace) {
-        if (newNamespace) {
-          // When the namespace changes we set the corresponding compute and update the databases
-          var found = self.availableComputes().some(function (compute) {
-            if (compute.namespace === newNamespace.id) {
-              if (!self.compute() || self.compute().id !== compute.id) {
-                self.compute(compute);
-                self.updateDatabases();
-              }
-              return true;
-            }
-          });
-          if (!found) {
-            self.compute(undefined);
-          }
-        }
-      })
-    });
+    self.database(typeof snippet.database !== "undefined" && snippet.database != null ? snippet.database : null);
 
     // History is currently in Notebook, same with saved queries by snippets, might be better in assist
     self.currentQueryTab = ko.observable(typeof snippet.currentQueryTab != "undefined" && snippet.currentQueryTab != null ? snippet.currentQueryTab : 'queryHistory');
@@ -579,9 +453,6 @@ var EditorViewModel = (function() {
       }
     };
 
-    self.isSqlDialect.subscribe(self.updateDatabases);
-    self.updateDatabases();
-
     huePubSub.subscribeOnce('assist.source.set', function (source) {
       if (source !== self.type()) {
         huePubSub.publish('assist.set.source', self.type());
@@ -3252,7 +3123,7 @@ var EditorViewModel = (function() {
     };
 
     self.isEditing = ko.observable(false);
-    self.isEditing.subscribe(function (newVal) {
+    self.isEditing.subscribe(function () {
       $(document).trigger("editingToggled");
     });
     self.toggleEditing = function () {
@@ -3344,25 +3215,6 @@ var EditorViewModel = (function() {
       })
     }, self.huePubSubId);
 
-    huePubSub.subscribe('context.catalog.namespaces.refreshed', function (sourceType) {
-      self.selectedNotebook().snippets().forEach(function (snippet) {
-        if (snippet.type() === sourceType) {
-          snippet.reloadNamespaces();
-        }
-      })
-    });
-
-    huePubSub.subscribe('data.catalog.entry.refreshed', function (details) {
-      var notebook = self.selectedNotebook();
-      if (details.entry.isSource() && notebook) {
-        notebook.snippets().forEach(function (snippet) {
-          if (details.entry.getSourceType() === snippet.type()) {
-            snippet.updateDatabases();
-          }
-        });
-      }
-    }, self.huePubSubId);
-
     huePubSub.subscribe('save.snippet.to.file', function() {
       withActiveSnippet(function (activeSnippet) {
         var data = {

+ 12 - 30
desktop/libs/notebook/src/notebook/templates/editor_components.mako

@@ -979,36 +979,18 @@ ${ sqlSyntaxDropdown.sqlSyntaxDropdown() }
 </script>
 
 <script type="text/html" id="snippet-header-database-selection">
-  <!-- ko if: window.HAS_MULTI_CLUSTER -->
-    <!-- ko if: loadingContext() && (isSqlDialect() || type() == 'spark2') -->
-    <i class="fa fa-spinner fa-spin muted"></i>
-    <!-- /ko -->
-
-    <!-- ko if: !loadingContext() && (isSqlDialect() || type() == 'spark2') -->
-    <!-- ko if: availableComputes().length > 0 -->
-    <span class="editor-header-title">${ _('Compute') }</span>
-    <div data-bind="component: { name: 'hue-drop-down', params: { value: compute, entries: availableComputes, labelAttribute: 'name', searchable: true, linkTitle: '${ _ko('Active compute') }' } }" style="display: inline-block"></div>
-    <!-- /ko -->
-    <!-- ko if: availableComputes().length === 0 -->
-    <span class="editor-header-title"><i class="fa fa-warning"></i> ${ _('No computes found') }</span>
-    <!-- /ko -->
-    <!-- /ko -->
-
-    <!-- ko if: availableNamespaces().length > 0 -->
-    <span class="editor-header-title">${ _('Namespace') }</span>
-    <div data-bind="component: { name: 'hue-drop-down', params: { value: namespace, entries: availableNamespaces, labelAttribute: 'name', searchable: true, linkTitle: '${ _ko('Active namespace') }' } }" style="display: inline-block"></div>
-    <!-- /ko -->
-  <!-- /ko -->
-
-  <!-- ko if: isSqlDialect() -->
-    <!-- ko if: availableDatabases().length > 0 -->
-    <span class="editor-header-title">${ _('Database') }</span>
-    <div data-bind="component: { name: 'hue-drop-down', params: { value: database, entries: availableDatabases, foreachVisible: true, searchable: true, linkTitle: '${ _ko('Active database') }' } }" style="display: inline-block"></div>
-    <!-- /ko -->
-    <!-- ko if: availableDatabases().length === 0 -->
-    <span class="editor-header-title"><i class="fa fa-warning"></i> ${ _('No databases found') }</span>
-    <!-- /ko -->
-    <!-- /ko -->
+  <!-- ko if: isSqlDialect() || type() == 'spark2' -->
+    <!-- ko component: {
+      name: 'hue-context-selector',
+      params: {
+        sourceType: type,
+        compute: compute,
+        namespace: namespace,
+        availableDatabases: availableDatabases,
+        database: database,
+        showDatabases: isSqlDialect()
+      }
+    } --><!-- /ko -->
   <!-- /ko -->
 </script>