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

HUE-7820 [metastore] Use the DataCatalog for the tag editor

Johan Ahlen 7 жил өмнө
parent
commit
1380500ecf

+ 1 - 3
apps/metastore/src/metastore/templates/metastore.mako

@@ -677,9 +677,7 @@ ${ components.menubar(is_embeddable) }
     <div class="span6 tile">
       <h4>${ _('Tags') }</h4>
       <div style="margin-top: 5px" data-bind="component: { name: 'nav-tags', params: {
-        sourceType: catalogEntry.dataCatalog.sourceType,
-        database: database.catalogEntry.name,
-        table: catalogEntry.name
+        catalogEntry: catalogEntry
       }}"></div>
     </div>
     <!-- /ko -->

+ 29 - 20
desktop/core/src/desktop/static/desktop/js/apiHelper.js

@@ -1803,6 +1803,35 @@ var ApiHelper = (function () {
     }, options)
   };
 
+  /**
+   * Lists all available navigator tags
+   *
+   * @param {Object} options
+   * @param {Function} options.successCallback
+   * @param {Function} [options.errorCallback]
+   * @param {boolean} [options.silenceErrors]
+   *
+   * @return {CancellablePromise}
+   */
+  ApiHelper.prototype.fetchAllNavigatorTags = function (options) {
+    var self = this;
+
+    var deferred = $.Deferred();
+
+    var request = self.simplePost(NAV_URLS.LIST_TAGS, undefined, {
+      silenceErrors: options.silenceErrors,
+      successCallback: function (data) {
+        if (data && data.tags) {
+          deferred.resolve(data.tags);
+        } else {
+          deferred.resolve({});
+        }
+      },
+      errorCallback: deferred.reject
+    });
+
+    return new CancellablePromise(deferred.promise(), request);
+  };
 
   ApiHelper.prototype.addNavTags = function (entityId, tags) {
     return $.post(NAV_URLS.ADD_TAGS, {
@@ -1890,26 +1919,6 @@ var ApiHelper = (function () {
     return new CancellablePromise(deferred.promise(), request);
   };
 
-
-
-  /**
-   * Lists all available navigator tags
-   *
-   * @param {Object} options
-   * @param {Function} options.successCallback
-   * @param {Function} [options.errorCallback]
-   * @param {boolean} [options.silenceErrors]
-   */
-  ApiHelper.prototype.listNavTags = function (options) {
-    var self = this;
-    fetchAssistData.bind(self)($.extend({ sourceType: 'nav' }, options, {
-      url: NAV_URLS.LIST_TAGS,
-      errorCallback: self.assistErrorCallback(options),
-      noCache: true
-    }));
-  };
-
-
   /**
    * Deprecated, use DataCatalog.getEntry(...).getSourceDetails()
    *

+ 73 - 3
desktop/core/src/desktop/static/desktop/js/dataCatalog.js

@@ -16,6 +16,8 @@
 
 var DataCatalog = (function () {
 
+  var STORAGE_POSTFIX = LOGGED_USERNAME // TODO: Add flag for embedded mode
+
   var DATA_CATALOG_VERSION = 1;
 
   var cacheEnabled = true;
@@ -30,7 +32,7 @@ var DataCatalog = (function () {
     self.sourceType = sourceType;
     self.entries = {};
     self.store = localforage.createInstance({
-      name: "HueDataCatalog_" + self.sourceType + '_' + LOGGED_USERNAME // TODO: Add flag for embedded mode
+      name: 'HueDataCatalog_' + self.sourceType + '_' + STORAGE_POSTFIX
     });
   }
 
@@ -771,7 +773,7 @@ var DataCatalog = (function () {
    *
    * @return {Promise}
    */
-  DataCatalogEntry.prototype.addNavMetaTags = function (tags) {
+  DataCatalogEntry.prototype.addNavigatorTags = function (tags) {
     var self = this;
     var deferred = $.Deferred();
     if (HAS_NAVIGATOR) {
@@ -803,7 +805,7 @@ var DataCatalog = (function () {
    *
    * @return {Promise}
    */
-  DataCatalogEntry.prototype.deleteNavMetaTags = function (tags) {
+  DataCatalogEntry.prototype.deleteNavigatorTags = function (tags) {
     var self = this;
     var deferred = $.Deferred();
     if (HAS_NAVIGATOR) {
@@ -1148,6 +1150,12 @@ var DataCatalog = (function () {
     }
   });
 
+  var allNavigatorTagsPromise = undefined;
+
+  var sharedDataCalogStore = localforage.createInstance({
+    name: 'HueDataCatalog_' + STORAGE_POSTFIX
+  });
+
   return {
     /**
      * @param options
@@ -1165,6 +1173,68 @@ var DataCatalog = (function () {
      */
     getCatalog : getCatalog,
 
+    /**
+     * @param {Object} [options]
+     * @param {boolean} [options.silenceErrors]
+     * @param {boolean} [options.refreshCache]
+     *
+     * @return {CancellablePromise}
+     */
+    getAllNavigatorTags: function (options) {
+      if (allNavigatorTagsPromise && (!options || !options.refreshCache)) {
+        return allNavigatorTagsPromise;
+      }
+
+      var deferred = $.Deferred();
+      allNavigatorTagsPromise = deferred.promise();
+
+      var reloadAllTags = function () {
+        ApiHelper.getInstance().fetchAllNavigatorTags({
+          silenceErrors: options && options.silenceErrors,
+        }).done(deferred.resolve).fail(deferred.reject);
+
+        deferred.done(function (allTags) {
+          sharedDataCalogStore.setItem('hue.dataCatalog.allNavTags', { allTags: allTags, hueTimestamp: Date.now(), version: DATA_CATALOG_VERSION });
+        })
+      };
+
+      if (!options || !options.refreshCache) {
+        sharedDataCalogStore.getItem('hue.dataCatalog.allNavTags').then(function (storeEntry) {
+          if (storeEntry && storeEntry.version === DATA_CATALOG_VERSION && (!storeEntry.hueTimestamp || (Date.now() - storeEntry.hueTimestamp) < CACHEABLE_TTL.default)) {
+            deferred.resolve(storeEntry.allTags);
+          } else {
+            reloadAllTags();
+          }
+        }).catch(reloadAllTags);
+      } else {
+        reloadAllTags();
+      }
+
+      return allNavigatorTagsPromise;
+    },
+
+    updateAllNavigatorTags: function (tagsToAdd, tagsToRemove) {
+      if (allNavigatorTagsPromise) {
+        allNavigatorTagsPromise.done(function (allTags) {
+          tagsToAdd.forEach(function (newTag) {
+            if (!allTags[newTag]) {
+              allTags[newTag] = 0;
+            }
+            allTags[newTag]++;
+          });
+          tagsToRemove.forEach(function (newTag) {
+            if (!allTags[tagsToRemove]) {
+              allTags[tagsToRemove]--;
+              if (allTags[tagsToRemove] === 0) {
+                delete allTags[tagsToRemove];
+              }
+            }
+          });
+          sharedDataCalogStore.setItem('hue.dataCatalog.allNavTags', allTags);
+        });
+      }
+    },
+
     enableCache: function () {
       cacheEnabled = true
     },

+ 9 - 2
desktop/core/src/desktop/templates/ko_components/ko_context_popover.mako

@@ -96,9 +96,9 @@ from metadata.conf import has_navigator
       <div class="context-popover-section" style="font-style: italic;" data-bind="text: comment"></div>
       <!-- /ko -->
       %if has_navigator(user):
-        <!-- ko if: $parent.sourceType === 'hive' || $parent.sourceType === 'impala' -->
+        <!-- ko if: ($parent.sourceType === 'hive' || $parent.sourceType === 'impala') && $parent.catalogEntry() -->
         <div class="context-popover-header">${ _("Tags") }</div>
-        <div class="context-popover-section" data-bind="component: { name: 'nav-tags', params: $parent } "></div>
+        <div class="context-popover-section" data-bind="component: { name: 'nav-tags', params: { catalogEntry: $parent.catalogEntry() } } "></div>
         <!-- /ko -->
       %endif
       <!-- ko if: typeof viewSql !== 'undefined' -->
@@ -524,6 +524,13 @@ from metadata.conf import has_navigator
         self.fetchedData = ko.observable();
         self.loading = ko.observable(false);
         self.hasErrors = ko.observable(false);
+
+        self.catalogEntry = ko.observable();
+
+        if (self.identifierChain.length > 0) {
+          var path = $.map(self.identifierChain, function (identifier) { return identifier.name });
+          DataCatalog.getEntry({ sourceType: sourceType, path: path }).done(self.catalogEntry);
+        }
       }
 
       GenericTabContents.prototype.formatAnalysisValue = function (type, val) {

+ 69 - 133
desktop/core/src/desktop/templates/ko_components/ko_nav_tags.mako

@@ -33,10 +33,10 @@ from django.utils.translation import ugettext as _
           hasErrors: hasErrors,
           errorMessage: '${_ko("Tags could not be loaded.")}',
           setTags: currentTags,
-          onSave: onSave,
+          onSave: saveTags.bind($data),
           validRegExp: '^[a-zA-z0-9_\-]{1,50}$',
           invalidMessage: '${_ko("Tags can only contain 1 to 50 alphanumeric characters, '_' or '-'.")}',
-          load: loadTags
+          load: getSelectizeTags
         }"></textarea>
        <div class="selectize-error" style="display: none;"><i class="fa fa-exclamation-triangle"></i> <span class="message"></span></div>
      </div>
@@ -46,162 +46,98 @@ from django.utils.translation import ugettext as _
     (function () {
       /**
        * @param {object} params
-       * @param {String} sourceType
-       * @param {String} defaultDatabase
-       * @param {object[]} [params.identifierChain]
-       * @param {String} [params.database]
-       * @param {String} [params.table]
-       * @param {String} [params.column]
+       * @param {DataCatalogEntry} [params.catalogEntry]
        *
        * @constructor
        */
       function NavTags(params) {
         var self = this;
-        var apiHelper = ApiHelper.getInstance();
 
-        var identifierChain = ko.unwrap(params.identifierChain);
-        if (! params.identifierChain) {
-          identifierChain = [];
-          if (params.database) {
-            identifierChain.push({ name: ko.unwrap(params.database) });
-          }
-          if (params.table) {
-            identifierChain.push({ name: ko.unwrap(params.table) });
-          }
-          if (params.column) {
-            identifierChain.push({ name: ko.unwrap(params.column) });
-          }
-        }
-
-        self.identity;
         self.hasErrors = ko.observable(false);
         self.loading = ko.observable(true);
-        self.navEntity = ko.observable();
+
         self.currentTags = ko.observableArray();
         self.allTags = ko.observableArray();
 
-        var fetchNavEntity = function () {
-          var fetchDeferral = $.Deferred();
-          var path = $.map(identifierChain, function (identifier) { return identifier.name });
-
-          apiHelper.containsDatabase(params.sourceType, path[0]).done(function (firstIsDatabase) {
-            if (!firstIsDatabase) {
-              path.unshift(ko.unwrap(params.defaultDatabase));
-            }
-            apiHelper.fetchNavigatorMetadata({
-              path: path,
-              isView: typeof params.fetchedData !== 'undefined' && typeof params.fetchedData() !== 'undefined' && params.fetchedData().is_view,
-              silenceErrors: true,
-              noCache: true
-            }).done(function (data) {
-              fetchDeferral.resolve(data);
-            }).fail(fetchDeferral.reject);
-          });
-          return fetchDeferral;
-        };
-
+        self.catalogEntry = params.catalogEntry;
 
-        var fetchAllTags = function () {
-          var fetchDeferral = $.Deferred();
-          apiHelper.listNavTags({
-            successCallback: function (data) {
-              fetchDeferral.resolve(Object.keys(data.tags));
-            },
-            silenceErrors: true,
-            errorCallback: function (error) {
-              hueUtils.logError(error);
-              fetchDeferral.reject()
-            }
-          });
-          return fetchDeferral;
+        self.getSelectizeTags = function (query, callback) {
+          callback($.map(self.allTags(), function (tag) { return { value: tag, text: tag }}));
         };
 
+        self.loadTags();
+      }
+
+      NavTags.prototype.loadTags = function () {
+        var self = this;
         self.loading(true);
-        $.when(fetchNavEntity(), fetchAllTags()).done(function (entity, allTags) {
-          self.identity = entity.identity;
-          self.currentTags(entity.tags);
-          self.allTags(allTags);
+        self.hasErrors(false);
+
+        var currentTagsPromise = self.catalogEntry.getNavigatorMeta().done(function (navigatorMeta) {
+          self.currentTags((navigatorMeta && navigatorMeta.tags) || []);
         }).fail(function () {
           self.hasErrors(true);
-        }).always(function () {
+        });
+
+        var allTagsPromise = DataCatalog.getAllNavigatorTags({ silenceErrors: true }).done(function (tagList) {
+          self.allTags(Object.keys(tagList));
+        }).fail(function () {
+          self.allTags([]);
+        });
+
+        $.when(currentTagsPromise, allTagsPromise).always(function () {
           self.loading(false);
         });
+      };
 
-        self.loadTags = function (query, callback) {
-          callback($.map(self.allTags(), function (tag) { return { value: tag, text: tag }}));
-        };
+      NavTags.prototype.saveTags = function (value) {
+        var self = this;
+        var newTags = value.length > 0 ? value.split(',') : [];
+        var tagsToRemove = [];
+        var tagsToAdd = [];
 
-        self.onSave = function (value) {
-          self.loading(true);
-          var newTags = value.length > 0 ? value.split(',') : [];
-          var tagsToRemove = [];
-          var tagsToAdd = [];
-          var tagIndex = {};
-          self.currentTags().forEach(function (tag) {
-            tagIndex[tag] = false;
-          });
-          newTags.forEach(function (newTag) {
-            if (typeof tagIndex[newTag] !== 'undefined') {
-              tagIndex[newTag] = true;
-            } else {
-              tagsToAdd.push(newTag);
-            }
-          });
-          Object.keys(tagIndex).forEach(function (oldTag) {
-            if (! tagIndex[oldTag]) {
-              tagsToRemove.push(oldTag);
-            }
-          });
-
-          self.loading(true);
-          self.hasErrors(false);
-          var addTagsDeferral = $.Deferred();
-          if (tagsToAdd.length > 0) {
-            if (typeof self.identity === 'undefined' || self.identity === null) {
-              addTagsDeferral.reject('${ _("Can\'t add tags without an entity.") }');
-            } else {
-              addTagsDeferral = apiHelper.addNavTags(self.identity, tagsToAdd);
-            }
+        var tagIndex = {};
+        self.currentTags().forEach(function (tag) {
+          tagIndex[tag] = false;
+        });
+        newTags.forEach(function (newTag) {
+          if (typeof tagIndex[newTag] !== 'undefined') {
+            tagIndex[newTag] = true;
           } else {
-            addTagsDeferral.resolve();
+            tagsToAdd.push(newTag);
           }
-
-          var removeTagsDeferral = $.Deferred();
-          if (tagsToRemove.length > 0) {
-            if (typeof self.identity === 'undefined' || self.identity === null) {
-              removeTagsDeferral.reject('Can\'t remove tags without an entity');
-            } else {
-              removeTagsDeferral = apiHelper.deleteNavTags(self.identity, tagsToRemove);
-            }
-          } else {
-            removeTagsDeferral.resolve();
+        });
+        Object.keys(tagIndex).forEach(function (oldTag) {
+          if (!tagIndex[oldTag]) {
+            tagsToRemove.push(oldTag);
           }
+        });
 
-          var fetchAllTagsDeferral = $.Deferred();
-
-          $.when(addTagsDeferral, removeTagsDeferral).done(function () {
-            self.currentTags(newTags);
-            fetchAllTags().done(function (tags) {
-              self.allTags(tags);
-              fetchAllTagsDeferral.resolve();
-            }).fail(fetchAllTagsDeferral.reject);
-          }).fail(fetchAllTagsDeferral.reject);
-
-          $.when(addTagsDeferral, removeTagsDeferral, fetchAllTagsDeferral).fail(function (addTagsError, removeTagsError) {
-            if (typeof addTagsError !== 'undefined') {
-              hueUtils.logError(addTagsError);
-              $(document).trigger('error', '${ _("Could not add tags, see the server log for details.") }');
-            }
-            if (typeof removeTagsError !== 'undefined') {
-              hueUtils.logError(removeTagsError);
-              $(document).trigger('error', '${ _("Could not remove tags, see the server log for details.") }');
-            }
-            self.hasErrors(true);
-          }).always(function () {
-            self.loading(false);
-          });
-        };
-      }
+        self.loading(true);
+        self.hasErrors(false);
+
+        var addTagsPromise = tagsToAdd.length > 0 ? self.catalogEntry.addNavigatorTags(tagsToAdd) : $.Deferred().resolve().promise();
+
+        var deleteTagsPromise = tagsToRemove.length > 0 ? self.catalogEntry.deleteNavigatorTags(tagsToRemove) : $.Deferred().resolve().promise();
+
+        addTagsPromise.fail(function (error) {
+          hueUtils.logError(error);
+          $(document).trigger('error', '${ _("Could not add tags, see the server log for details.") }');
+        });
+
+        deleteTagsPromise.fail(function (error) {
+          hueUtils.logError(error);
+          $(document).trigger('error', '${ _("Could not remove tags, see the server log for details.") }');
+        });
+
+        $.when(addTagsPromise, deleteTagsPromise).done(function () {
+          if (tagsToAdd.length || tagsToRemove.length) {
+            DataCatalog.updateAllNavigatorTags(tagsToAdd, tagsToRemove);
+          }
+          self.loading(false);
+          self.loadTags();
+        });
+      };
 
       ko.components.register('nav-tags', {
         viewModel: NavTags,