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

HUE-8893 [assist] Move HueDocument and HueFileEntry into webpack

Johan Ahlen 6 жил өмнө
parent
commit
e1f765c309
22 өөрчлөгдсөн 1089 нэмэгдсэн , 36 устгасан
  1. 0 2
      apps/beeswax/src/beeswax/templates/create_database.mako
  2. 0 2
      apps/beeswax/src/beeswax/templates/create_table_manually.mako
  3. 0 3
      apps/beeswax/src/beeswax/templates/execute.mako
  4. 0 2
      apps/beeswax/src/beeswax/templates/import_wizard_choose_delimiter.mako
  5. 0 2
      apps/beeswax/src/beeswax/templates/import_wizard_choose_file.mako
  6. 0 2
      apps/beeswax/src/beeswax/templates/import_wizard_define_columns.mako
  7. 0 1
      apps/jobbrowser/src/jobbrowser/templates/job_browser.mako
  8. 0 2
      apps/metastore/src/metastore/templates/metastore.mako
  9. 240 0
      desktop/core/src/desktop/js/doc/hueDocument.js
  10. 844 0
      desktop/core/src/desktop/js/doc/hueFileEntry.js
  11. 4 0
      desktop/core/src/desktop/js/hue.js
  12. 0 5
      desktop/core/src/desktop/templates/assist.mako
  13. 0 2
      desktop/core/src/desktop/templates/assist_m.mako
  14. 0 1
      desktop/core/src/desktop/templates/home2.mako
  15. 0 1
      desktop/core/src/desktop/templates/hue.mako
  16. 1 3
      desktop/libs/indexer/src/indexer/templates/importer.mako
  17. 0 2
      desktop/libs/indexer/src/indexer/templates/indexer.mako
  18. 0 1
      desktop/libs/indexer/src/indexer/templates/indexes.mako
  19. 0 1
      desktop/libs/indexer/src/indexer/templates/topics.mako
  20. 0 1
      desktop/libs/notebook/src/notebook/templates/editor.mako
  21. 0 2
      desktop/libs/notebook/src/notebook/templates/editor_m.mako
  22. 0 1
      desktop/libs/notebook/src/notebook/templates/notebook.mako

+ 0 - 2
apps/beeswax/src/beeswax/templates/create_database.mako

@@ -32,8 +32,6 @@ ${layout.metastore_menubar()}
 <script src="${ static('desktop/js/hue.routie.js') }" type="text/javascript" charset="utf-8"></script>
 <script src="${ static('metastore/js/metastore.ko.js') }"></script>
 
-${ assist.assistJSModels() }
-
 <link rel="stylesheet" href="${ static('metastore/css/metastore.css') }" type="text/css">
 <link rel="stylesheet" href="${ static('notebook/css/notebook.css') }">
 <link rel="stylesheet" href="${ static('notebook/css/notebook-layout.css') }">

+ 0 - 2
apps/beeswax/src/beeswax/templates/create_table_manually.mako

@@ -30,8 +30,6 @@ ${ layout.metastore_menubar() }
 
 <script src="${ static('metastore/js/metastore.ko.js') }"></script>
 
-${ assist.assistJSModels() }
-
 <link rel="stylesheet" href="${ static('metastore/css/metastore.css') }">
 <link rel="stylesheet" href="${ static('notebook/css/notebook.css') }">
 <link rel="stylesheet" href="${ static('notebook/css/notebook-layout.css') }">

+ 0 - 3
apps/beeswax/src/beeswax/templates/execute.mako

@@ -793,9 +793,6 @@ ${ commonshare() | n,unicode }
 </script>
 
 <script src="${ static('desktop/js/hue.routie.js') }" type="text/javascript" charset="utf-8"></script>
-
-${ assist.assistJSModels() }
-
 <script src="${ static('beeswax/js/beeswax.vm.js') }"></script>
 <script src="${ static('desktop/js/share.vm.js') }"></script>
 %if ENABLE_QUERY_BUILDER.get():

+ 0 - 2
apps/beeswax/src/beeswax/templates/import_wizard_choose_delimiter.mako

@@ -30,8 +30,6 @@ ${ layout.metastore_menubar() }
 
 <script src="${ static('metastore/js/metastore.ko.js') }"></script>
 
-${ assist.assistJSModels() }
-
 <link rel="stylesheet" href="${ static('metastore/css/metastore.css') }" type="text/css">
 <link rel="stylesheet" href="${ static('notebook/css/notebook.css') }">
 <link rel="stylesheet" href="${ static('notebook/css/notebook-layout.css') }">

+ 0 - 2
apps/beeswax/src/beeswax/templates/import_wizard_choose_file.mako

@@ -29,8 +29,6 @@ ${ layout.metastore_menubar() }
 
 <script src="${ static('metastore/js/metastore.ko.js') }"></script>
 
-${ assist.assistJSModels() }
-
 <link rel="stylesheet" href="${ static('metastore/css/metastore.css') }" type="text/css">
 <link rel="stylesheet" href="${ static('notebook/css/notebook.css') }">
 <link rel="stylesheet" href="${ static('notebook/css/notebook-layout.css') }">

+ 0 - 2
apps/beeswax/src/beeswax/templates/import_wizard_define_columns.mako

@@ -30,8 +30,6 @@ ${ layout.metastore_menubar() }
 
 <script src="${ static('metastore/js/metastore.ko.js') }"></script>
 
-${ assist.assistJSModels() }
-
 <link rel="stylesheet" href="${ static('metastore/css/metastore.css') }" type="text/css">
 <link rel="stylesheet" href="${ static('notebook/css/notebook.css') }">
 <link rel="stylesheet" href="${ static('notebook/css/notebook-layout.css') }">

+ 0 - 1
apps/jobbrowser/src/jobbrowser/templates/job_browser.mako

@@ -65,7 +65,6 @@ ${ commonheader("Job Browser", "jobbrowser", user, request) | n,unicode }
 % endif
 
 % if not is_embeddable:
-  ${ assist.assistJSModels() }
   ${ assist.assistPanel() }
 
   <a title="${_('Toggle Assist')}" class="pointer show-assist" data-bind="visible: !$root.isLeftPanelVisible() && $root.assistAvailable(), click: function() { $root.isLeftPanelVisible(true); }">

+ 0 - 2
apps/metastore/src/metastore/templates/metastore.mako

@@ -43,8 +43,6 @@ ${ commonheader(_("Metastore"), app_name, user, request) | n,unicode }
 
 <script src="${ static('desktop/ext/js/jquery/plugins/jquery.mousewheel.min.js') }"></script>
 
-${ assist.assistJSModels() }
-
 <link rel="stylesheet" href="${ static('notebook/css/notebook-layout.css') }">
 <style type="text/css">
 % if conf.CUSTOM.BANNER_TOP_HTML.get():

+ 240 - 0
desktop/core/src/desktop/js/doc/hueDocument.js

@@ -0,0 +1,240 @@
+// 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 $ from 'jquery';
+import ko from 'knockout';
+
+import apiHelper from 'api/apiHelper';
+import hueUtils from 'utils/hueUtils';
+
+class HueDocument {
+  /**
+   *
+   * @param {Object} options
+   * @param {HueFileEntry} options.fileEntry
+   *
+   * @constructor
+   */
+  constructor(options) {
+    this.fileEntry = options.fileEntry;
+    this.definition = ko.observable();
+    this.loaded = ko.observable(false);
+    this.loading = ko.observable(false);
+    this.hasErrors = ko.observable(false);
+    this.searchInput = ko.observable();
+    this.selectedUserOrGroup = ko.observable();
+    this.selectedPerm = ko.observable('read');
+
+    this.userMap = {};
+    this.idToUserMap = {};
+    this.groupMap = {};
+    this.items = [];
+  }
+
+  onShareAutocompleteUserEnter() {
+    const searchAutoCompInput = $('#userSearchAutocomp').val();
+    const selectedUserOrGroup = self.userMap[searchAutoCompInput]
+      ? self.userMap[searchAutoCompInput]
+      : self.groupMap[searchAutoCompInput];
+    if (selectedUserOrGroup != null) {
+      if (typeof selectedUserOrGroup.username !== 'undefined') {
+        this.definition().perms[self.selectedPerm()].users.push(selectedUserOrGroup);
+      } else {
+        this.definition().perms[self.selectedPerm()].groups.push(selectedUserOrGroup);
+      }
+      this.persistPerms();
+    }
+    $('#userSearchAutocomp').val('');
+  }
+
+  shareAutocompleteUserSource(request, callback) {
+    const self = this;
+    const successCallback = jsonUserGroups => {
+      self.items = [];
+      jsonUserGroups.users.forEach(user => {
+        const label = self.prettifyUsername(user);
+        const highLightedLabel = hueUtils.highlight(label, request.term);
+        self.userMap[label] = user;
+        self.items.push({
+          data: {
+            icon: 'fa fa-user',
+            label: highLightedLabel
+          },
+          value: label
+        });
+        self.idToUserMap[user.id] = user;
+      });
+      jsonUserGroups.groups.forEach(group => {
+        self.groupMap[group.name] = group;
+        const highLightedLabel = hueUtils.highlight(group.name, request.term);
+        self.items.push({
+          data: {
+            icon: 'fa fa-users',
+            label: highLightedLabel
+          },
+          value: group.name
+        });
+      });
+
+      if (self.items.length === 0) {
+        self.items.push({
+          noMatch: true
+        });
+      }
+
+      callback(self.items);
+    };
+
+    apiHelper.fetchUsersAndGroups({
+      data: { filter: request.term },
+      successCallback: successCallback,
+      errorCallback: () => {}
+    });
+  }
+
+  persistPerms() {
+    const self = this;
+    const postPerms = {
+      read: {
+        user_ids: self.definition().perms.read.users.map(user => user.id),
+        group_ids: self.definition().perms.read.groups.map(group => group.id)
+      },
+      write: {
+        user_ids: self.definition().perms.write.users.map(user => user.id),
+        group_ids: self.definition().perms.write.groups.map(group => group.id)
+      }
+    };
+
+    $.post(
+      '/desktop/api2/doc/share',
+      {
+        uuid: JSON.stringify(self.fileEntry.definition().uuid),
+        data: JSON.stringify(postPerms)
+      },
+      response => {
+        if (response != null) {
+          if (response.status !== 0) {
+            $(document).trigger(
+              'error',
+              'There was an error processing your action: ' + response.message
+            );
+          } else {
+            self.load();
+          }
+        }
+      }
+    ).fail(response => {
+      $(document).trigger(
+        'error',
+        'There was an error processing your action: ' + response.responseText
+      );
+    });
+  }
+
+  load() {
+    const self = this;
+    if (self.loading()) {
+      return;
+    }
+
+    self.loading(true);
+    self.hasErrors(false);
+
+    const fetchDocumentsSuccessCallback = data => {
+      const readUsers = data.document.perms.read.users.map(user => user.id);
+      const writeUsers = data.document.perms.write.users.map(user => user.id);
+      const allUsers = readUsers.concat(writeUsers);
+      if (allUsers.length > 0) {
+        apiHelper.fetchUsersByIds({
+          userids: JSON.stringify(allUsers),
+          successCallback: response => {
+            response.users.forEach(user => {
+              // Needed for getting prettyusername of already shared users
+              self.idToUserMap[user.id] = user;
+            });
+            self.definition(data.document);
+          },
+          errorCallback: () => {}
+        });
+      } else {
+        self.definition(data.document);
+      }
+    };
+
+    apiHelper
+      .fetchDocument({
+        uuid: self.fileEntry.definition().uuid
+      })
+      .done(data => {
+        fetchDocumentsSuccessCallback(data);
+        self.loading(false);
+        self.loaded(true);
+      })
+      .fail(() => {
+        self.hasErrors(true);
+        self.loading(false);
+        self.loaded(true);
+      });
+  }
+
+  prettifyUsernameById(id) {
+    return this.prettifyUsername(this.idToUserMap[id]);
+  }
+
+  prettifyUsername(user) {
+    user.prettyName = '';
+    if (user.first_name) {
+      user.prettyName += user.first_name + ' ';
+    }
+    if (user.last_name) {
+      user.prettyName += user.last_name + ' ';
+    }
+    if (user.prettyName) {
+      user.prettyName += '(' + user.username + ')';
+    } else {
+      user.prettyName += user.username;
+    }
+    return user.prettyName;
+  }
+
+  removeFromPerms(perms, id) {
+    const self = this;
+    $(perms).each((cnt, item) => {
+      if (item.id === id) {
+        perms.splice(cnt, 1);
+      }
+    });
+    self.persistPerms();
+  }
+
+  removeUserReadShare(user) {
+    this.removeFromPerms(this.definition().perms.read.users, user.id);
+  }
+
+  removeUserWriteShare(user) {
+    this.removeFromPerms(this.definition().perms.write.users, user.id);
+  }
+
+  removeGroupReadShare(group) {
+    this.removeFromPerms(this.definition().perms.read.groups, group.id);
+  }
+
+  removeGroupWriteShare(group) {
+    this.removeFromPerms(this.definition().perms.write.groups, group.id);
+  }
+}
+
+export default HueDocument;

+ 844 - 0
desktop/core/src/desktop/js/doc/hueFileEntry.js

@@ -0,0 +1,844 @@
+// 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 $ from 'jquery';
+import ko from 'knockout';
+
+import apiHelper from 'api/apiHelper';
+import huePubSub from 'utils/huePubSub';
+import HueDocument from 'doc/hueDocument';
+import { DOCUMENT_TYPE_I18n, DOCUMENT_TYPES } from 'doc/docSupport';
+
+const SORTS = {
+  defaultAsc: (a, b) => {
+    if (a.isDirectory() && !b.isDirectory()) {
+      return -1;
+    }
+    if (!a.isDirectory() && b.isDirectory()) {
+      return 1;
+    }
+    if (a.isDirectory() && b.isDirectory()) {
+      return SORTS.nameAsc(a, b);
+    }
+    return SORTS.lastModifiedDesc(a, b);
+  },
+  nameAsc: (a, b) => a.definition().name.localeCompare(b.definition().name),
+  nameDesc: (a, b) => SORTS.nameAsc(b, a),
+  descriptionAsc: (a, b) => a.definition().description.localeCompare(b.definition().description),
+  descriptionDesc: (a, b) => SORTS.descriptionAsc(b, a),
+  typeAsc: (a, b) => a.definition().type.localeCompare(b.definition().type),
+  typeDesc: (a, b) => SORTS.typeAsc(b, a),
+  ownerAsc: (a, b) => a.definition().owner.localeCompare(b.definition().owner),
+  ownerDesc: (a, b) => SORTS.ownerAsc(b, a),
+  lastModifiedAsc: (a, b) => a.definition().last_modified_ts - b.definition().last_modified_ts,
+  lastModifiedDesc: (a, b) => SORTS.lastModifiedAsc(b, a)
+};
+
+class HueFileEntry {
+  /**
+   *
+   * @param {Object} options
+   * @param {Object} options.definition
+   * @param {Function} options.activeEntry - The observable keeping track of the current open directory
+   * @param {Function} options.trashEntry - The observable keeping track of the trash directory
+   * @param {HueFolder} options.parent
+   * @param {string} options.app - Currently only 'documents' is supported
+   * @param {string} options.user
+   *
+   * @constructor
+   */
+  constructor(options) {
+    this.activeEntry = options.activeEntry;
+    this.trashEntry = options.trashEntry;
+    this.parent = options.parent;
+    this.definition = ko.observable(options.definition);
+    this.app = options.app;
+    this.user = options.user;
+    this.superuser = options.superuser;
+    this.serverTypeFilter = options.serverTypeFilter || ko.observable({ type: 'all' });
+    this.statsVisible = ko.observable(false);
+    this.highlight = ko.observable(false);
+
+    this.document = ko.observable();
+    this.selectedDocsWithDependents = ko.observable([]);
+    this.importedDocSummary = ko.observable();
+    this.showTable = ko.observable();
+    this.entries = ko.observableArray([]);
+
+    // Filter is only used in the assist panel at the moment
+    this.isFilterVisible = ko.observable(false);
+    this.filter = ko.observable('').extend({ rateLimit: 400 });
+
+    this.typeFilter = options.typeFilter || ko.observable(DOCUMENT_TYPES[0]); // First one is always 'all'
+
+    this.isFilterVisible.subscribe(newValue => {
+      if (!newValue && this.filter()) {
+        this.filter('');
+      }
+    });
+
+    this.filteredEntries = ko.pureComputed(() => {
+      const filter = this.filter().toLowerCase();
+      const typeFilter = this.typeFilter().type;
+      if (filter || typeFilter !== 'all') {
+        return this.entries().filter(entry => {
+          const entryType = entry.definition().type;
+          return (
+            (typeFilter === 'all' || entryType === typeFilter || entryType === 'directory') &&
+            (!filter ||
+              entry
+                .definition()
+                .name.toLowerCase()
+                .indexOf(filter) !== -1 ||
+              (DOCUMENT_TYPE_I18n[entryType] &&
+                DOCUMENT_TYPE_I18n[entryType].toLowerCase().indexOf(filter) !== -1) ||
+              entry
+                .definition()
+                .description.toLowerCase()
+                .indexOf(filter) !== -1)
+          );
+        });
+      }
+      return this.entries();
+    });
+
+    this.getSelectedDocsWithDependents = () => {
+      this.selectedDocsWithDependents([]);
+      const uuids = this.selectedEntries()
+        .map(entry => entry.definition().uuid)
+        .join(',');
+
+      const data = {
+        uuids: uuids,
+        data: 'false',
+        dependencies: 'true'
+      };
+
+      $.get('/desktop/api2/doc/', data, response => {
+        const docsWithDependents = [];
+        if (response && response.data_list) {
+          for (let index = 0; index < response.data_list.length; index++) {
+            docsWithDependents.push({
+              name: response.data_list[index].document.name,
+              dependents: response.data_list[index].dependents
+            });
+          }
+        }
+        this.selectedDocsWithDependents(docsWithDependents);
+      }).fail(response => {
+        $(document).trigger('error', 'Error getting document data: ' + response.responseText);
+      });
+    };
+
+    this.isTrash = ko.pureComputed(() => this.definition().name === '.Trash');
+
+    this.isTrashed = ko.pureComputed(() => {
+      if (typeof this.parent !== 'undefined' && this.parent !== null) {
+        return this.parent.isTrash() || this.parent.isTrashed();
+      }
+      return false;
+    });
+
+    this.isRoot = ko.pureComputed(() => this.definition().name === '');
+
+    this.isDirectory = ko.pureComputed(() => this.definition().type === 'directory');
+
+    this.isShared = ko.pureComputed(() => {
+      const perms = this.definition().perms;
+      return (
+        perms &&
+        (perms.read.users.length > 0 ||
+          perms.read.groups.length > 0 ||
+          perms.write.users.length > 0 ||
+          perms.write.groups.length > 0)
+      );
+    });
+
+    this.isSharedWithMe = ko.pureComputed(() => this.user !== this.definition().owner);
+
+    this.canModify = ko.pureComputed(() => {
+      const perms = this.definition().perms;
+      return (
+        !this.isSharedWithMe() ||
+        this.superuser ||
+        (perms &&
+          (perms.write.users.some(user => user.username === this.user) ||
+            perms.write.groups.some(
+              writeGroup => LOGGED_USERGROUPS.indexOf(writeGroup.name) !== -1
+            )))
+      );
+    });
+
+    this.canReshare = ko.pureComputed(() => {
+      if (!this.isSharedWithMe()) {
+        return true;
+      }
+      let target = this;
+      while (target.parent && !target.isShared()) {
+        target = target.parent;
+      }
+      return target.canModify();
+    });
+
+    this.entriesToDelete = ko.observableArray();
+    this.deletingEntries = ko.observable(false);
+
+    this.selected = ko.observable(false);
+
+    this.loaded = ko.observable(false);
+    this.loading = ko.observable(false);
+    this.hasErrors = ko.observable(false);
+
+    this.uploading = ko.observable(false);
+    this.uploadComplete = ko.observable(false);
+    this.uploadFailed = ko.observable(false);
+    this.selectedImportFile = ko.observable('');
+
+    this.importEnabled = ko.pureComputed(() => this.selectedImportFile() !== '');
+
+    this.activeSort = options.activeSort;
+
+    this.selectedEntries = ko.pureComputed(() => this.entries().filter(entry => entry.selected()));
+
+    this.sharedWithMeSelected = ko.pureComputed(() =>
+      this.selectedEntries().some(entry => entry.isSharedWithMe())
+    );
+
+    this.directorySelected = ko.pureComputed(() =>
+      this.selectedEntries().some(entry => entry.isDirectory())
+    );
+
+    this.selectedEntry = ko.pureComputed(() => {
+      if (this.selectedEntries().length === 1) {
+        return this.selectedEntries()[0];
+      }
+      return null;
+    });
+
+    this.breadcrumbs = ko.pureComputed(() => {
+      const result = [];
+      let lastParent = this.parent;
+      while (lastParent) {
+        result.unshift(lastParent);
+        lastParent = lastParent.parent;
+      }
+      return result;
+    });
+  }
+
+  getDirectory() {
+    if (!this.definition() || this.isRoot()) {
+      return null;
+    } else {
+      return this.definition().uuid;
+    }
+  }
+
+  highlightInside(uuid) {
+    const self = this;
+    self.typeFilter(DOCUMENT_TYPES[0]);
+    let foundEntry;
+    self.entries().forEach(entry => {
+      entry.highlight(false);
+      if (entry.definition() && entry.definition().uuid === uuid) {
+        foundEntry = entry;
+      }
+    });
+    if (foundEntry) {
+      if (foundEntry.definition().type === 'directory') {
+        self.activeEntry(foundEntry);
+        if (!foundEntry.entries().length) {
+          foundEntry.load();
+        }
+      } else {
+        window.setTimeout(() => {
+          huePubSub.subscribeOnce('assist.db.scrollToComplete', () => {
+            foundEntry.highlight(true);
+            // Timeout is for animation effect
+            window.setTimeout(() => {
+              foundEntry.highlight(false);
+            }, 1800);
+          });
+          huePubSub.publish('assist.db.scrollTo', foundEntry);
+        }, 0);
+      }
+    }
+  }
+
+  showContextPopover(entry, event, positionAdjustment) {
+    const self = this;
+    const $source = $(event.target);
+    const offset = $source.offset();
+    if (positionAdjustment) {
+      offset.left += positionAdjustment.left;
+      offset.top += positionAdjustment.top;
+    }
+
+    self.statsVisible(true);
+    huePubSub.publish('context.popover.show', {
+      data: {
+        type: 'hue',
+        definition: self.definition()
+      },
+      showInAssistEnabled: false,
+      orientation: 'right',
+      pinEnabled: false,
+      source: {
+        element: event.target,
+        left: offset.left,
+        top: offset.top - 3,
+        right: offset.left + $source.width() + 1,
+        bottom: offset.top + $source.height() - 3
+      }
+    });
+    huePubSub.subscribeOnce('context.popover.hidden', () => {
+      self.statsVisible(false);
+    });
+  }
+
+  addDirectoryParamToUrl(url) {
+    const self = this;
+    const directoryId = self.getDirectory();
+
+    if (!directoryId) {
+      return url;
+    } else if (url.indexOf('?') !== -1) {
+      return url + '&directory_uuid=' + self.definition().uuid;
+    } else {
+      return url + '?directory_uuid=' + self.definition().uuid;
+    }
+  }
+
+  beforeContextOpen() {
+    if (!this.selected()) {
+      this.parent.selectedEntries().forEach(entry => {
+        entry.selected(false);
+      });
+      this.selected(true);
+    }
+  }
+
+  showNewDirectoryModal() {
+    if (!this.isTrash() && !this.isTrashed() && this.canModify()) {
+      $('#createDirectoryModal').modal('show');
+    }
+  }
+
+  showRenameDirectoryModal() {
+    let selectedEntry = this;
+    if (!selectedEntry.selected()) {
+      selectedEntry = selectedEntry.selectedEntry();
+    }
+    if (!selectedEntry.isTrash() && !selectedEntry.isTrashed() && selectedEntry.canModify()) {
+      $('#renameDirectoryName').val(selectedEntry.definition().name);
+      $('#renameDirectoryModal').modal('show');
+    }
+  }
+
+  setSort(name) {
+    if (this.activeSort().indexOf(name) === -1) {
+      if (name === 'lastModified') {
+        this.activeSort('lastModifiedDesc');
+      } else {
+        this.activeSort(name + 'Asc');
+      }
+    } else if (name !== 'lastModified' && this.activeSort().indexOf('Asc') !== -1) {
+      this.activeSort(name + 'Desc');
+    } else if (name === 'lastModified' && this.activeSort().indexOf('Desc') !== -1) {
+      this.activeSort('lastModifiedAsc');
+    } else {
+      this.activeSort('defaultAsc');
+    }
+    this.entries.sort(SORTS[this.activeSort()]);
+  }
+
+  showSharingModal(entry) {
+    if (entry) {
+      this.selectedEntries().forEach(otherEntry => {
+        if (otherEntry !== entry) {
+          otherEntry.selected(false);
+        }
+      });
+      entry.selected(true);
+    }
+    if (this.selectedEntry()) {
+      this.selectedEntry().loadDocument();
+      $('#shareDocumentModal').modal('show');
+    }
+  }
+
+  copy() {
+    const self = this;
+    if (self.selectedEntries().indexOf(self) !== -1) {
+      self.activeEntry(self.parent);
+    }
+    const copyNext = () => {
+      if (self.selectedEntries().length > 0) {
+        const nextUuid = self
+          .selectedEntries()
+          .shift()
+          .definition().uuid;
+        apiHelper.copyDocument({
+          uuid: nextUuid,
+          successCallback: () => {
+            copyNext();
+          },
+          errorCallback: () => {
+            self.activeEntry().load();
+          }
+        });
+      } else {
+        huePubSub.publish('assist.document.refresh');
+        self.activeEntry().load();
+      }
+    };
+    copyNext();
+  }
+
+  loadDocument() {
+    this.document(new HueDocument({ fileEntry: this }));
+    this.document().load();
+  }
+
+  /**
+   * @param {HueFileEntry[]} entries
+   */
+  moveHere(entries) {
+    const self = this;
+    if (self.app === 'documents') {
+      const moveNext = () => {
+        if (entries.length > 0) {
+          const nextId = entries.shift().definition().uuid;
+          apiHelper.moveDocument({
+            successCallback: () => {
+              moveNext();
+            },
+            errorCallback: () => {
+              self.activeEntry().load();
+            },
+            sourceId: nextId,
+            destinationId: self.definition().uuid
+          });
+        } else {
+          huePubSub.publish('assist.document.refresh');
+          if (self !== self.activeEntry()) {
+            self.load();
+          }
+          self.activeEntry().load();
+        }
+      };
+      moveNext();
+    }
+  }
+
+  createNewEntry(options) {
+    return new HueFileEntry(
+      $.extend(
+        {
+          activeEntry: this.activeEntry,
+          activeSort: this.activeSort,
+          trashEntry: this.trashEntry,
+          serverTypeFilter: this.serverTypeFilter,
+          typeFilter: this.typeFilter,
+          app: this.app,
+          user: this.user,
+          superuser: this.superuser
+        },
+        options
+      )
+    );
+  }
+
+  search(query) {
+    const self = this;
+
+    const owner = self.definition().isSearchResult ? self.parent : self;
+
+    if (!query) {
+      if (self.definition().isSearchResult) {
+        self.activeEntry(self.parent);
+      }
+      return;
+    }
+
+    const resultEntry = self.createNewEntry({
+      definition: {
+        isSearchResult: true,
+        name: '"' + query + '"'
+      },
+      parent: owner
+    });
+
+    self.activeEntry(resultEntry);
+
+    resultEntry.loading(true);
+
+    apiHelper.searchDocuments({
+      uuid: owner.uuid,
+      query: query,
+      type: self.serverTypeFilter().type,
+      successCallback: function(data) {
+        resultEntry.hasErrors(false);
+        const newEntries = [];
+
+        data.documents.forEach(definition => {
+          const entry = self.createNewEntry({
+            definition: definition,
+            parent: self
+          });
+          if (!entry.isTrash()) {
+            newEntries.push(entry);
+          }
+        });
+
+        resultEntry.entries(newEntries);
+        resultEntry.loading(false);
+        resultEntry.loaded(true);
+      },
+      errorCallback: () => {
+        resultEntry.hasErrors(true);
+        resultEntry.loading(false);
+        resultEntry.loaded(true);
+      }
+    });
+  }
+
+  toggleSelected() {
+    this.selected(!this.selected());
+  }
+
+  openSelected() {
+    if (this.selectedEntries().length === 1) {
+      this.selectedEntry().open();
+    }
+  }
+
+  open() {
+    if (this.definition().type === 'directory') {
+      this.makeActive();
+      huePubSub.publish('file.browser.directory.opened');
+      if (!this.loaded()) {
+        this.load();
+      }
+    } else {
+      huePubSub.publish('open.link', this.definition().absoluteUrl);
+    }
+  }
+
+  load(successCallback, errorCallback, silenceErrors) {
+    const self = this;
+    if (self.loading()) {
+      return;
+    }
+    self.loading(true);
+
+    if (self.app === 'documents') {
+      apiHelper.fetchDocuments({
+        uuid: self.definition().uuid,
+        type: self.serverTypeFilter().type,
+        silenceErrors: !!silenceErrors,
+        successCallback: data => {
+          self.definition(data.document);
+          self.hasErrors(false);
+          const newEntries = [];
+
+          data.children.forEach(definition => {
+            const entry = self.createNewEntry({
+              definition: definition,
+              parent: self
+            });
+            if (entry.isTrash()) {
+              self.trashEntry(entry);
+            } else {
+              newEntries.push(entry);
+            }
+          });
+
+          newEntries.sort(SORTS[self.activeSort()]);
+          self.entries(newEntries);
+          if (!self.parent && data.parent) {
+            self.parent = self.createNewEntry({
+              definition:
+                data.parent.path === '/' && self.isSharedWithMe() ? { name: '/' } : data.parent,
+              parent: null
+            });
+          }
+          self.loading(false);
+          self.loaded(true);
+
+          if (
+            self.isRoot() &&
+            self.entries().length === 1 &&
+            self.entries()[0].definition().type === 'directory' &&
+            self.entries()[0].isSharedWithMe()
+          ) {
+            self.activeEntry(self.entries()[0]);
+            self.activeEntry().load(successCallback);
+          } else if (successCallback && typeof successCallback === 'function') {
+            successCallback();
+          }
+        },
+        errorCallback: () => {
+          self.hasErrors(true);
+          self.loading(false);
+          self.loaded(true);
+          if (errorCallback) {
+            errorCallback();
+          }
+        }
+      });
+    }
+  }
+
+  showTrash() {
+    if (this.trashEntry()) {
+      this.trashEntry().open();
+    }
+  }
+
+  emptyTrash() {
+    if (this.trashEntry()) {
+      if (!this.trashEntry().loaded()) {
+        this.trashEntry().load(function() {
+          this.entriesToDelete(this.trashEntry().entries());
+          $('#deleteEntriesModal').modal('show');
+        });
+      } else {
+        this.entriesToDelete(this.trashEntry().entries());
+        $('#deleteEntriesModal').modal('show');
+      }
+    }
+  }
+
+  moveToTrash() {
+    if (this.selectedEntries().length > 0 && (this.superuser || !this.sharedWithMeSelected())) {
+      this.entriesToDelete(this.selectedEntries());
+      this.removeDocuments(false);
+    }
+  }
+
+  showRestoreConfirmation() {
+    if (this.selectedEntries().length > 0 && (this.superuser || !this.sharedWithMeSelected())) {
+      $('#restoreFromTrashModal').modal('show');
+    }
+  }
+
+  showDeleteConfirmation() {
+    if (this.selectedEntries().length > 0 && (this.superuser || !this.sharedWithMeSelected())) {
+      this.entriesToDelete(this.selectedEntries());
+      $('#deleteEntriesModal').modal('show');
+    }
+  }
+
+  removeDocuments(deleteForever) {
+    const self = this;
+    if (self.entriesToDelete().indexOf(self) !== -1) {
+      self.activeEntry(self.parent);
+    }
+
+    const deleteNext = () => {
+      if (self.entriesToDelete().length > 0) {
+        const nextUuid = self
+          .entriesToDelete()
+          .shift()
+          .definition().uuid;
+        apiHelper.deleteDocument({
+          uuid: nextUuid,
+          skipTrash: deleteForever,
+          successCallback: () => {
+            deleteNext();
+          },
+          errorCallback: () => {
+            self.activeEntry().load();
+            $('#deleteEntriesModal').modal('hide');
+            $('.modal-backdrop').remove();
+            self.deletingEntries(false);
+          }
+        });
+      } else {
+        self.deletingEntries(false);
+        huePubSub.publish('assist.document.refresh');
+        $('#deleteEntriesModal').modal('hide');
+        $('.modal-backdrop').remove();
+        self.activeEntry().load();
+      }
+    };
+    self.deletingEntries(true);
+    deleteNext();
+  }
+
+  closeUploadModal() {
+    if (this.app === 'documents') {
+      $('#importDocumentsModal').modal('hide');
+      $('#importDocumentData').modal('hide');
+      $('#importDocumentInput').val('');
+    }
+    // Allow the modal to hide
+    window.setTimeout(() => {
+      this.uploading(false);
+      this.uploadComplete(false);
+      this.uploadFailed(false);
+    }, 400);
+  }
+
+  upload() {
+    const self = this;
+    if (
+      document.getElementById('importDocumentInput').files.length > 0 &&
+      self.app === 'documents'
+    ) {
+      self.uploading(true);
+      self.uploadComplete(false);
+      self.uploadFailed(false);
+      self.importedDocSummary(null);
+      self.showTable(false);
+      apiHelper.uploadDocument({
+        formData: new FormData($('#importDocumentsForm')[0]),
+        successCallback: data => {
+          self.uploading(false);
+          self.uploadComplete(true);
+          huePubSub.publish('assist.document.refresh');
+          self.load();
+
+          $('#importDocumentsModal').modal('hide');
+          $('#importDocumentData').modal('show');
+
+          self.importedDocSummary(data);
+        },
+        progressHandler: event => {
+          $('#importDocumentsProgress').val(Math.round((event.loaded / event.total) * 100));
+        },
+        errorCallback: () => {
+          self.uploading(false);
+          self.uploadComplete(true);
+          self.uploadFailed(true);
+        }
+      });
+    }
+  }
+
+  importedDocumentCount() {
+    if (this.importedDocSummary()) {
+      return this.importedDocSummary()['documents'].length;
+    }
+    return 0;
+  }
+
+  toggleShowTable() {
+    this.showTable(!this.showTable());
+  }
+
+  makeActive() {
+    if (!this.loaded()) {
+      this.load();
+    }
+    this.activeEntry(this);
+  }
+
+  showUploadModal() {
+    if (this.app === 'documents' && !this.isTrash() && !this.isTrashed()) {
+      $('#importDocumentsModal').modal('show');
+    }
+  }
+
+  contextMenuDownload() {
+    if (this.selected()) {
+      this.parent.download();
+    } else {
+      this.downloadThis();
+    }
+  }
+
+  downloadThis() {
+    window.location.href =
+      window.HUE_BASE_URL +
+      '/desktop/api2/doc/export?documents=' +
+      ko.mapping.toJSON([this.definition().id]);
+  }
+
+  download() {
+    if (this.app === 'documents') {
+      if (this.selectedEntries().length > 0) {
+        const ids = this.selectedEntries().map(entry => entry.definition().id);
+        window.location.href =
+          window.HUE_BASE_URL + '/desktop/api2/doc/export?documents=' + ko.mapping.toJSON(ids);
+      } else {
+        this.downloadThis();
+      }
+    }
+  }
+
+  restoreFromTrash() {
+    const self = this;
+    if (self.app === 'documents') {
+      if (self.selectedEntries().indexOf(self) !== -1) {
+        self.activeEntry(self.parent);
+      }
+
+      if (self.selectedEntries().length > 0) {
+        const uuids = self
+          .selectedEntries()
+          .map(entry => entry.definition().uuid)
+          .join(',');
+        apiHelper.restoreDocument({
+          uuids: uuids,
+          successCallback: () => {
+            huePubSub.publish('assist.document.refresh');
+            self.activeEntry().load();
+          },
+          errorCallback: () => {
+            self.activeEntry().load();
+          }
+        });
+      } else {
+        self.activeEntry().load();
+      }
+      $('#restoreFromTrashModal').modal('hide');
+    }
+  }
+
+  createDirectory(name) {
+    const self = this;
+    if (name && self.app === 'documents') {
+      apiHelper.createDocumentsFolder({
+        successCallback: function() {
+          huePubSub.publish('assist.document.refresh');
+          self.load();
+        },
+        parentUuid: self.definition().uuid,
+        name: name
+      });
+      $('#newDirectoryName').val(null);
+    }
+  }
+
+  renameDirectory(name) {
+    const self = this;
+    if (name && self.app === 'documents') {
+      apiHelper.updateDocument({
+        successCallback: function() {
+          huePubSub.publish('assist.document.refresh');
+          self.load();
+        },
+        uuid: self.definition().uuid,
+        name: name
+      });
+      $('#renameDirectoryName').val(null);
+    }
+  }
+}
+
+export default HueFileEntry;

+ 4 - 0
desktop/core/src/desktop/js/hue.js

@@ -69,6 +69,8 @@ import HdfsAutocompleter from 'utils/hdfsAutocompleter';
 import SqlAutocompleter from 'sql/sqlAutocompleter';
 import sqlAutocompleteParser from 'parse/sqlAutocompleteParser'; // Notebook and used throughout via hue-simple-ace-editor ko component
 import sqlStatementsParser from 'parse/sqlStatementsParser'; // In search.ko and notebook.ko
+import HueFileEntry from 'doc/hueFileEntry';
+import HueDocument from 'doc/hueDocument';
 
 // TODO: Migrate away
 window._ = _;
@@ -91,7 +93,9 @@ window.HdfsAutocompleter = HdfsAutocompleter;
 window.hueAnalytics = hueAnalytics;
 window.HueColors = HueColors;
 window.hueDebug = hueDebug;
+window.HueDocument = HueDocument;
 window.hueDrop = hueDrop;
+window.HueFileEntry = HueFileEntry;
 window.HueGeo = HueGeo;
 window.huePubSub = huePubSub;
 window.hueUtils = hueUtils;

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

@@ -32,11 +32,6 @@ from desktop.views import _ko
 
 <%namespace name="sqlDocIndex" file="/sql_doc_index.mako" />
 
-<%def name="assistJSModels()">
-<script src="${ static('desktop/js/document/hueDocument.js') }"></script>
-<script src="${ static('desktop/js/document/hueFileEntry.js') }"></script>
-</%def>
-
 <%def name="assistPanel(is_s3_enabled=False)">
   <%
     # TODO remove

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

@@ -25,8 +25,6 @@ ${ commonheader_m(_('Assist'), 'assist', user, request) | n,unicode }
 
 <script src="${ static('metastore/js/metastore.ko.js') }"></script>
 
-${ assist.assistJSModels() }
-
 ${ assist.assistPanel() }
 
 <style type="text/css">

+ 0 - 1
desktop/core/src/desktop/templates/home2.mako

@@ -25,7 +25,6 @@
 
 %if not is_embeddable:
 ${ commonheader(_('Welcome Home'), "home", user, request) | n,unicode }
-${ assist.assistJSModels() }
 ${ assist.assistPanel() }
 %endif
 

+ 0 - 1
desktop/core/src/desktop/templates/hue.mako

@@ -504,7 +504,6 @@ ${ commonHeaderFooterComponents.header_pollers(user, is_s3_enabled, apps) }
 ## clusterConfig makes an Ajax call so it needs to be after commonHeaderFooterComponents
 <script src="${ static('desktop/js/clusterConfig.js') }"></script>
 
-${ assist.assistJSModels() }
 ${ assist.assistPanel() }
 
 % if request is not None:

+ 1 - 3
desktop/libs/indexer/src/indexer/templates/importer.mako

@@ -33,8 +33,6 @@ ${ commonheader(_("Importer"), "indexer", user, request, "60px") | n,unicode }
 
 <script src="${ static('metastore/js/metastore.ko.js') }"></script>
 
-${ assist.assistJSModels() }
-
 <link rel="stylesheet" href="${ static('notebook/css/notebook.css') }">
 <link rel="stylesheet" href="${ static('notebook/css/notebook-layout.css') }">
 ${ assist.assistPanel() }
@@ -3083,4 +3081,4 @@ ${ assist.assistPanel() }
 
 %if not is_embeddable:
 ${ commonfooter(request, messages) | n,unicode }
-%endif
+%endif

+ 0 - 2
desktop/libs/indexer/src/indexer/templates/indexer.mako

@@ -29,8 +29,6 @@ ${ commonheader(_("Solr Indexes"), "search", user, request, "60px") | n,unicode
 
 <script src="${ static('metastore/js/metastore.ko.js') }"></script>
 
-${ assist.assistJSModels() }
-
 <link rel="stylesheet" href="${ static('notebook/css/notebook.css') }">
 <link rel="stylesheet" href="${ static('notebook/css/notebook-layout.css') }">
 

+ 0 - 1
desktop/libs/indexer/src/indexer/templates/indexes.mako

@@ -32,7 +32,6 @@ MAIN_SCROLLABLE = is_embeddable and ".page-content" or ".content-panel"
 ${ commonheader(_("Index Browser"), "search", user, request, "60px") | n,unicode }
 <script src="${ static('desktop/ext/js/jquery/plugins/jquery.mousewheel.min.js') }"></script>
 
-${ assist.assistJSModels() }
 <link rel="stylesheet" href="${ static('notebook/css/notebook-layout.css') }">
 <style type="text/css">
 % if conf.CUSTOM.BANNER_TOP_HTML.get():

+ 0 - 1
desktop/libs/indexer/src/indexer/templates/topics.mako

@@ -32,7 +32,6 @@ MAIN_SCROLLABLE = is_embeddable and ".page-content" or ".content-panel"
 ${ commonheader(_("Streams Browser"), "search", user, request, "60px") | n,unicode }
 <script src="${ static('desktop/ext/js/jquery/plugins/jquery.mousewheel.min.js') }"></script>
 
-${ assist.assistJSModels() }
 <link rel="stylesheet" href="${ static('notebook/css/notebook-layout.css') }">
 <style type="text/css">
 % if conf.CUSTOM.BANNER_TOP_HTML.get():

+ 0 - 1
desktop/libs/notebook/src/notebook/templates/editor.mako

@@ -38,7 +38,6 @@ ${ editorComponents.commonHTML(is_embeddable=is_embeddable, suffix='editor') }
 
 %if not is_embeddable:
 ${ assist.assistPanel() }
-${ assist.assistJSModels() }
 ${ configKoComponents.config() }
 ${ notebookKoComponents.aceKeyboardShortcuts() }
 ${ notebookKoComponents.downloadSnippetResults() }

+ 0 - 2
desktop/libs/notebook/src/notebook/templates/editor_m.mako

@@ -160,8 +160,6 @@ ${ commonheader_m(editor_type, editor_type, user, request, "68px") | n,unicode }
 
 </div>
 
-${ assist.assistJSModels() }
-
 <script type="text/javascript">
   if (ko.options) {
     ko.options.deferUpdates = true;

+ 0 - 1
desktop/libs/notebook/src/notebook/templates/notebook.mako

@@ -54,7 +54,6 @@ ${ editorComponents.topBar(suffix='notebook') }
 
 %if not is_embeddable:
 ${ assist.assistPanel() }
-${ assist.assistJSModels() }
 ${ configKoComponents.config() }
 ${ notebookKoComponents.aceKeyboardShortcuts() }
 ${ notebookKoComponents.downloadSnippetResults() }