Pārlūkot izejas kodu

HUE-8893 [assist] Extract the language ref and functions panel into separate components

Johan Ahlen 6 gadi atpakaļ
vecāks
revīzija
179546585c

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

@@ -0,0 +1,334 @@
+// 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 componentUtils from 'ko/components/componentUtils';
+import huePubSub from 'utils/huePubSub';
+import I18n from 'utils/i18n';
+
+const TEMPLATE = `
+  <script type="text/html" id="language-reference-topic-tree">
+    <!-- ko if: $data.length -->
+    <ul class="assist-docs-topic-tree " data-bind="foreach: $data">
+      <li>
+        <a class="black-link" href="javascript: void(0);" data-bind="click: function () { $component.selectedTopic($data); }, toggle: open">
+          <i class="fa fa-fw" style="font-size: 12px;" data-bind="css: { 'fa-chevron-right': children.length && !open(), 'fa-chevron-down': children.length && open() }"></i>
+          <span class="assist-field-link" href="javascript: void(0);" data-bind="css: { 'blue': $component.selectedTopic() === $data }, text: title"></span>
+        </a>
+        <!-- ko if: open -->
+        <!-- ko template: { name: 'language-reference-topic-tree', data: children } --><!-- /ko -->
+        <!-- /ko -->
+      </li>
+    </ul>
+    <!-- /ko -->
+  </script>
+
+  <div class="assist-inner-panel">
+    <div class="assist-flex-panel">
+      <div class="assist-flex-header">
+        <div class="assist-inner-header">
+          <div class="function-dialect-dropdown" data-bind="component: { name: 'hue-drop-down', params: { fixedPosition: true, value: sourceType, entries: availableTypes, linkTitle: '${I18n(
+            'Selected dialect'
+          )}' } }" style="display: inline-block"></div>
+        </div>
+      </div>
+      <div class="assist-flex-search">
+        <div class="assist-filter">
+          <input class="clearable" type="text" placeholder="Filter..." data-bind="clearable: query, value: query, valueUpdate: 'afterkeydown'">
+        </div>
+      </div>
+      <div class="assist-docs-topics" data-bind="css: { 'assist-flex-fill': !selectedTopic(), 'assist-flex-40': selectedTopic() }">
+        <!-- ko ifnot: query -->
+        <!-- ko template: { name: 'language-reference-topic-tree', data: topics } --><!-- /ko -->
+        <!-- /ko -->
+        <!-- ko if: query -->
+        <!-- ko if: filteredTopics().length > 0 -->
+        <ul class="assist-docs-topic-tree" data-bind="foreach: filteredTopics">
+          <li>
+            <a class="assist-field-link" href="javascript: void(0);" data-bind="css: { 'blue': $component.selectedTopic() === $data }, click: function () { $component.selectedTopic($data); }, html: titleMatch() || title"></a>
+          </li>
+        </ul>
+        <!-- /ko -->
+        <!-- ko if: filteredTopics().length === 0 -->
+        <ul class="assist-docs-topic-tree">
+          <li class="assist-no-entries">${I18n('No results found.')}</li>
+        </ul>
+        <!-- /ko -->
+        <!-- /ko -->
+      </div>
+      <!-- ko if: selectedTopic -->
+      <div class="assist-flex-60 assist-docs-details" data-bind="with: selectedTopic">
+        <div class="assist-panel-close"><button class="close" data-bind="click: function() { $component.selectedTopic(undefined); }">&times;</button></div>
+        <div data-bind="html: bodyMatch() || body()"></div>
+      </div>
+      <!-- /ko -->
+    </div>
+  </div>
+`;
+
+class LanguageReferenceTopic {
+  constructor(entry, index) {
+    this.ref = entry.ref;
+    this.title = entry.title;
+    this.index = index;
+    this.weight = 1;
+    this.children = [];
+    entry.children.forEach(child => {
+      this.children.push(new LanguageReferenceTopic(child, self.index));
+    });
+    this.loadDeferred = $.Deferred();
+    this.loading = ko.observable(false);
+    this.body = ko.observable();
+    this.bodyMatch = ko.observable();
+    this.open = ko.observable(false);
+    this.titleMatch = ko.observable();
+  }
+
+  load() {
+    if (this.body() || this.loading()) {
+      return this.loadDeferred.promise();
+    }
+    this.loading(true);
+    apiHelper
+      .simpleGet(this.index[this.ref])
+      .done(doc => {
+        this.body(doc.body);
+      })
+      .always(() => {
+        this.loading(false);
+        this.loadDeferred.resolve(this);
+      });
+    return this.loadDeferred.promise();
+  }
+}
+
+class LanguageReferencePanel {
+  constructor(params, element) {
+    this.disposals = [];
+    this.availableTypes = ['impala', 'hive'];
+    this.sourceType = ko.observable('hive');
+    this.allTopics = {
+      impala: [],
+      hive: []
+    };
+
+    window.IMPALA_DOC_TOP_LEVEL.forEach(topLevelItem => {
+      this.allTopics.impala.push(new LanguageReferenceTopic(topLevelItem, window.IMPALA_DOC_INDEX));
+    });
+    window.HIVE_DOC_TOP_LEVEL.forEach(topLevelItem => {
+      this.allTopics.hive.push(new LanguageReferenceTopic(topLevelItem, window.HIVE_DOC_INDEX));
+    });
+
+    const updateType = type => {
+      if (this.availableTypes.indexOf(type) !== -1) {
+        this.sourceType(type);
+      }
+    };
+
+    const activeSnippetTypeSub = huePubSub.subscribe('active.snippet.type.changed', details => {
+      updateType(details.type);
+    });
+    this.disposals.push(() => {
+      activeSnippetTypeSub.remove();
+    });
+
+    huePubSub.subscribeOnce('set.active.snippet.type', updateType);
+    huePubSub.publish('get.active.snippet.type');
+
+    this.topics = ko.pureComputed(() => this.allTopics[this.sourceType()]);
+
+    this.selectedTopic = ko.observable();
+
+    const selectedSub = this.selectedTopic.subscribe(newTopic => {
+      if (newTopic) {
+        newTopic.load();
+      }
+    });
+    this.disposals.push(() => {
+      selectedSub.dispose();
+    });
+
+    this.query = ko.observable().extend({ throttle: 200 });
+    this.filteredTopics = ko.observableArray();
+
+    const sortFilteredTopics = () => {
+      this.filteredTopics.sort((a, b) => {
+        if (a.weight !== b.weight) {
+          return b.weight - a.weight;
+        }
+        return a.title.localeCompare(b.title);
+      });
+    };
+
+    this.query.subscribe(newVal => {
+      if (!newVal) {
+        return;
+      }
+      const lowerCaseQuery = this.query().toLowerCase();
+      const replaceRegexp = new RegExp('(' + lowerCaseQuery + ')', 'i');
+      this.filteredTopics([]);
+
+      let sortTimeout = -1;
+
+      const findInside = topic => {
+        topic.load().done(loadedTopic => {
+          let match = false;
+          const titleIndex = loadedTopic.title.toLowerCase().indexOf(lowerCaseQuery);
+          if (titleIndex !== -1) {
+            loadedTopic.weight = titleIndex === 0 ? 2 : 1;
+            loadedTopic.titleMatch(
+              loadedTopic.title.replace(new RegExp('(' + lowerCaseQuery + ')', 'i'), '<b>$1</b>')
+            );
+            loadedTopic.bodyMatch(undefined);
+            this.filteredTopics.push(loadedTopic);
+            match = true;
+          } else if (
+            loadedTopic.body() &&
+            loadedTopic
+              .body()
+              .toLowerCase()
+              .indexOf(lowerCaseQuery) !== -1
+          ) {
+            loadedTopic.weight = 0;
+            loadedTopic.titleMatch(undefined);
+            loadedTopic.bodyMatch(loadedTopic.body().replace(replaceRegexp, '<b>$1</b>'));
+            this.filteredTopics.push(loadedTopic);
+            match = true;
+          } else {
+            loadedTopic.titleMatch(undefined);
+            loadedTopic.bodyMatch(undefined);
+          }
+          if (match) {
+            window.clearTimeout(sortTimeout);
+            sortTimeout = window.setTimeout(sortFilteredTopics, 100);
+          }
+        });
+
+        topic.children.forEach(findInside);
+      };
+
+      this.topics.forEach(findInside);
+
+      window.setTimeout(() => {
+        // Initial sort deferred for promises to complete
+        sortFilteredTopics();
+      }, 0);
+    });
+
+    const selectedTopicSub = this.selectedTopic.subscribe(() => {
+      $(element)
+        .find('.assist-docs-details')
+        .scrollTop(0);
+    });
+
+    const querySub = this.query.subscribe(() => {
+      $(element)
+        .find('.assist-docs-topics')
+        .scrollTop(0);
+    });
+
+    const scrollToSelectedTopic = function() {
+      const topics = $(element).find('.assist-docs-topics');
+      if (topics.find('.blue').length) {
+        topics.scrollTop(
+          Math.min(
+            topics.scrollTop() + topics.find('.blue').position().top - 20,
+            topics.find('> ul').height() - topics.height()
+          )
+        );
+      }
+    };
+
+    const scrollToAnchor = function(anchorId) {
+      if (!anchorId) {
+        return;
+      }
+      const detailsPanel = $(element).find('.assist-docs-details');
+      const found = detailsPanel.find('#' + anchorId.split('/').join(' #'));
+      if (found.length) {
+        detailsPanel.scrollTop(found.position().top - 10);
+      }
+    };
+
+    huePubSub.subscribe('scroll.test', scrollToSelectedTopic);
+
+    const showTopicSub = huePubSub.subscribe('assist.lang.ref.panel.show.topic', targetTopic => {
+      const topicStack = [];
+      const findTopic = topics => {
+        topics.some(topic => {
+          topicStack.push(topic);
+          if (topic.ref === targetTopic.ref) {
+            while (topicStack.length) {
+              topicStack.pop().open(true);
+            }
+            this.query('');
+            this.selectedTopic(topic);
+            window.setTimeout(() => {
+              scrollToAnchor(targetTopic.anchorId);
+              scrollToSelectedTopic();
+            }, 0);
+            return true;
+          } else if (topic.children.length) {
+            const inChild = findTopic(topic.children);
+            if (inChild) {
+              return true;
+            }
+          }
+          topicStack.pop();
+        });
+      };
+      findTopic(this.topics());
+    });
+
+    $(element).on('click.langref', event => {
+      if (event.target.className === 'hue-doc-internal-link') {
+        huePubSub.publish('assist.lang.ref.panel.show.topic', {
+          ref: $(event.target).data('doc-ref'),
+          anchorId: $(event.target).data('doc-anchor-id')
+        });
+      }
+    });
+
+    this.disposals.push(() => {
+      selectedTopicSub.dispose();
+      querySub.dispose();
+      showTopicSub.remove();
+      $(element).off('click.langref');
+    });
+  }
+
+  dispose() {
+    while (this.disposals.length) {
+      this.disposals.pop()();
+    }
+  }
+}
+
+let instance;
+
+const viewModelFactory = {
+  createViewModel: (params, componentInfo) => {
+    if (!instance) {
+      instance = new LanguageReferencePanel(params, componentInfo.element);
+    }
+    return instance;
+  }
+};
+
+componentUtils.registerComponent('language-reference-panel', viewModelFactory, TEMPLATE);

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

@@ -0,0 +1,334 @@
+// 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 componentUtils from 'ko/components/componentUtils';
+import huePubSub from 'utils/huePubSub';
+import I18n from 'utils/i18n';
+
+const TEMPLATE = `
+  <script type="text/html" id="language-reference-topic-tree">
+    <!-- ko if: $data.length -->
+    <ul class="assist-docs-topic-tree " data-bind="foreach: $data">
+      <li>
+        <a class="black-link" href="javascript: void(0);" data-bind="click: function () { $component.selectedTopic($data); }, toggle: open">
+          <i class="fa fa-fw" style="font-size: 12px;" data-bind="css: { 'fa-chevron-right': children.length && !open(), 'fa-chevron-down': children.length && open() }"></i>
+          <span class="assist-field-link" href="javascript: void(0);" data-bind="css: { 'blue': $component.selectedTopic() === $data }, text: title"></span>
+        </a>
+        <!-- ko if: open -->
+        <!-- ko template: { name: 'language-reference-topic-tree', data: children } --><!-- /ko -->
+        <!-- /ko -->
+      </li>
+    </ul>
+    <!-- /ko -->
+  </script>
+
+  <div class="assist-inner-panel">
+    <div class="assist-flex-panel">
+      <div class="assist-flex-header">
+        <div class="assist-inner-header">
+          <div class="function-dialect-dropdown" data-bind="component: { name: 'hue-drop-down', params: { fixedPosition: true, value: sourceType, entries: availableTypes, linkTitle: '${I18n(
+            'Selected dialect'
+          )}' } }" style="display: inline-block"></div>
+        </div>
+      </div>
+      <div class="assist-flex-search">
+        <div class="assist-filter">
+          <input class="clearable" type="text" placeholder="Filter..." data-bind="clearable: query, value: query, valueUpdate: 'afterkeydown'">
+        </div>
+      </div>
+      <div class="assist-docs-topics" data-bind="css: { 'assist-flex-fill': !selectedTopic(), 'assist-flex-40': selectedTopic() }">
+        <!-- ko ifnot: query -->
+        <!-- ko template: { name: 'language-reference-topic-tree', data: topics } --><!-- /ko -->
+        <!-- /ko -->
+        <!-- ko if: query -->
+        <!-- ko if: filteredTopics().length > 0 -->
+        <ul class="assist-docs-topic-tree" data-bind="foreach: filteredTopics">
+          <li>
+            <a class="assist-field-link" href="javascript: void(0);" data-bind="css: { 'blue': $component.selectedTopic() === $data }, click: function () { $component.selectedTopic($data); }, html: titleMatch() || title"></a>
+          </li>
+        </ul>
+        <!-- /ko -->
+        <!-- ko if: filteredTopics().length === 0 -->
+        <ul class="assist-docs-topic-tree">
+          <li class="assist-no-entries">${I18n('No results found.')}</li>
+        </ul>
+        <!-- /ko -->
+        <!-- /ko -->
+      </div>
+      <!-- ko if: selectedTopic -->
+      <div class="assist-flex-60 assist-docs-details" data-bind="with: selectedTopic">
+        <div class="assist-panel-close"><button class="close" data-bind="click: function() { $component.selectedTopic(undefined); }">&times;</button></div>
+        <div data-bind="html: bodyMatch() || body()"></div>
+      </div>
+      <!-- /ko -->
+    </div>
+  </div>
+`;
+
+class LanguageReferenceTopic {
+  constructor(entry, index) {
+    this.ref = entry.ref;
+    this.title = entry.title;
+    this.index = index;
+    this.weight = 1;
+    this.children = [];
+    entry.children.forEach(child => {
+      this.children.push(new LanguageReferenceTopic(child, self.index));
+    });
+    this.loadDeferred = $.Deferred();
+    this.loading = ko.observable(false);
+    this.body = ko.observable();
+    this.bodyMatch = ko.observable();
+    this.open = ko.observable(false);
+    this.titleMatch = ko.observable();
+  }
+
+  load() {
+    if (this.body() || this.loading()) {
+      return this.loadDeferred.promise();
+    }
+    this.loading(true);
+    apiHelper
+      .simpleGet(this.index[this.ref])
+      .done(doc => {
+        this.body(doc.body);
+      })
+      .always(() => {
+        this.loading(false);
+        this.loadDeferred.resolve(this);
+      });
+    return this.loadDeferred.promise();
+  }
+}
+
+class LanguageReferencePanel {
+  constructor(params, element) {
+    this.disposals = [];
+    this.availableTypes = ['impala', 'hive'];
+    this.sourceType = ko.observable('hive');
+    this.allTopics = {
+      impala: [],
+      hive: []
+    };
+
+    window.IMPALA_DOC_TOP_LEVEL.forEach(topLevelItem => {
+      this.allTopics.impala.push(new LanguageReferenceTopic(topLevelItem, window.IMPALA_DOC_INDEX));
+    });
+    window.HIVE_DOC_TOP_LEVEL.forEach(topLevelItem => {
+      this.allTopics.hive.push(new LanguageReferenceTopic(topLevelItem, window.HIVE_DOC_INDEX));
+    });
+
+    const updateType = type => {
+      if (this.availableTypes.indexOf(type) !== -1) {
+        this.sourceType(type);
+      }
+    };
+
+    const activeSnippetTypeSub = huePubSub.subscribe('active.snippet.type.changed', details => {
+      updateType(details.type);
+    });
+    this.disposals.push(() => {
+      activeSnippetTypeSub.remove();
+    });
+
+    huePubSub.subscribeOnce('set.active.snippet.type', updateType);
+    huePubSub.publish('get.active.snippet.type');
+
+    this.topics = ko.pureComputed(() => this.allTopics[this.sourceType()]);
+
+    this.selectedTopic = ko.observable();
+
+    const selectedSub = this.selectedTopic.subscribe(newTopic => {
+      if (newTopic) {
+        newTopic.load();
+      }
+    });
+    this.disposals.push(() => {
+      selectedSub.dispose();
+    });
+
+    this.query = ko.observable().extend({ throttle: 200 });
+    this.filteredTopics = ko.observableArray();
+
+    const sortFilteredTopics = () => {
+      this.filteredTopics.sort((a, b) => {
+        if (a.weight !== b.weight) {
+          return b.weight - a.weight;
+        }
+        return a.title.localeCompare(b.title);
+      });
+    };
+
+    this.query.subscribe(newVal => {
+      if (!newVal) {
+        return;
+      }
+      const lowerCaseQuery = this.query().toLowerCase();
+      const replaceRegexp = new RegExp('(' + lowerCaseQuery + ')', 'i');
+      this.filteredTopics([]);
+
+      let sortTimeout = -1;
+
+      const findInside = topic => {
+        topic.load().done(loadedTopic => {
+          let match = false;
+          const titleIndex = loadedTopic.title.toLowerCase().indexOf(lowerCaseQuery);
+          if (titleIndex !== -1) {
+            loadedTopic.weight = titleIndex === 0 ? 2 : 1;
+            loadedTopic.titleMatch(
+              loadedTopic.title.replace(new RegExp('(' + lowerCaseQuery + ')', 'i'), '<b>$1</b>')
+            );
+            loadedTopic.bodyMatch(undefined);
+            this.filteredTopics.push(loadedTopic);
+            match = true;
+          } else if (
+            loadedTopic.body() &&
+            loadedTopic
+              .body()
+              .toLowerCase()
+              .indexOf(lowerCaseQuery) !== -1
+          ) {
+            loadedTopic.weight = 0;
+            loadedTopic.titleMatch(undefined);
+            loadedTopic.bodyMatch(loadedTopic.body().replace(replaceRegexp, '<b>$1</b>'));
+            this.filteredTopics.push(loadedTopic);
+            match = true;
+          } else {
+            loadedTopic.titleMatch(undefined);
+            loadedTopic.bodyMatch(undefined);
+          }
+          if (match) {
+            window.clearTimeout(sortTimeout);
+            sortTimeout = window.setTimeout(sortFilteredTopics, 100);
+          }
+        });
+
+        topic.children.forEach(findInside);
+      };
+
+      this.topics.forEach(findInside);
+
+      window.setTimeout(() => {
+        // Initial sort deferred for promises to complete
+        sortFilteredTopics();
+      }, 0);
+    });
+
+    const selectedTopicSub = this.selectedTopic.subscribe(() => {
+      $(element)
+        .find('.assist-docs-details')
+        .scrollTop(0);
+    });
+
+    const querySub = this.query.subscribe(() => {
+      $(element)
+        .find('.assist-docs-topics')
+        .scrollTop(0);
+    });
+
+    const scrollToSelectedTopic = function() {
+      const topics = $(element).find('.assist-docs-topics');
+      if (topics.find('.blue').length) {
+        topics.scrollTop(
+          Math.min(
+            topics.scrollTop() + topics.find('.blue').position().top - 20,
+            topics.find('> ul').height() - topics.height()
+          )
+        );
+      }
+    };
+
+    const scrollToAnchor = function(anchorId) {
+      if (!anchorId) {
+        return;
+      }
+      const detailsPanel = $(element).find('.assist-docs-details');
+      const found = detailsPanel.find('#' + anchorId.split('/').join(' #'));
+      if (found.length) {
+        detailsPanel.scrollTop(found.position().top - 10);
+      }
+    };
+
+    huePubSub.subscribe('scroll.test', scrollToSelectedTopic);
+
+    const showTopicSub = huePubSub.subscribe('assist.lang.ref.panel.show.topic', targetTopic => {
+      const topicStack = [];
+      const findTopic = topics => {
+        topics.some(topic => {
+          topicStack.push(topic);
+          if (topic.ref === targetTopic.ref) {
+            while (topicStack.length) {
+              topicStack.pop().open(true);
+            }
+            this.query('');
+            this.selectedTopic(topic);
+            window.setTimeout(() => {
+              scrollToAnchor(targetTopic.anchorId);
+              scrollToSelectedTopic();
+            }, 0);
+            return true;
+          } else if (topic.children.length) {
+            const inChild = findTopic(topic.children);
+            if (inChild) {
+              return true;
+            }
+          }
+          topicStack.pop();
+        });
+      };
+      findTopic(this.topics());
+    });
+
+    $(element).on('click.langref', event => {
+      if (event.target.className === 'hue-doc-internal-link') {
+        huePubSub.publish('assist.lang.ref.panel.show.topic', {
+          ref: $(event.target).data('doc-ref'),
+          anchorId: $(event.target).data('doc-anchor-id')
+        });
+      }
+    });
+
+    this.disposals.push(() => {
+      selectedTopicSub.dispose();
+      querySub.dispose();
+      showTopicSub.remove();
+      $(element).off('click.langref');
+    });
+  }
+
+  dispose() {
+    while (this.disposals.length) {
+      this.disposals.pop()();
+    }
+  }
+}
+
+let instance;
+
+const viewModelFactory = {
+  createViewModel: (params, componentInfo) => {
+    if (!instance) {
+      instance = new LanguageReferencePanel(params, componentInfo.element);
+    }
+    return instance;
+  }
+};
+
+componentUtils.registerComponent('language-reference-panel', viewModelFactory, TEMPLATE);

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

@@ -30,495 +30,10 @@ from desktop.lib.i18n import smart_unicode
 from desktop.views import _ko
 %>
 
-<%namespace name="sqlDocIndex" file="/sql_doc_index.mako" />
 
-<%def name="assistPanel(is_s3_enabled=False)">
-  <script type="text/html" id="language-reference-topic-tree">
-    <!-- ko if: $data.length -->
-    <ul class="assist-docs-topic-tree " data-bind="foreach: $data">
-      <li>
-        <a class="black-link" href="javascript: void(0);" data-bind="click: function () { $component.selectedTopic($data); }, toggle: open">
-          <i class="fa fa-fw" style="font-size: 12px;" data-bind="css: { 'fa-chevron-right': children.length && !open(), 'fa-chevron-down': children.length && open() }"></i>
-          <span class="assist-field-link" href="javascript: void(0);" data-bind="css: { 'blue': $component.selectedTopic() === $data }, text: title"></span>
-        </a>
-        <!-- ko if: open -->
-        <!-- ko template: { name: 'language-reference-topic-tree', data: children } --><!-- /ko -->
-        <!-- /ko -->
-      </li>
-    </ul>
-    <!-- /ko -->
-  </script>
-
-  <script type="text/html" id="language-reference-panel-template">
-    <div class="assist-inner-panel">
-      <div class="assist-flex-panel">
-        <div class="assist-flex-header">
-          <div class="assist-inner-header">
-            <div class="function-dialect-dropdown" data-bind="component: { name: 'hue-drop-down', params: { fixedPosition: true, value: sourceType, entries: availableTypes, linkTitle: '${ _ko('Selected dialect') }' } }" style="display: inline-block"></div>
-          </div>
-        </div>
-        <div class="assist-flex-search">
-          <div class="assist-filter">
-            <input class="clearable" type="text" placeholder="Filter..." data-bind="clearable: query, value: query, valueUpdate: 'afterkeydown'">
-          </div>
-        </div>
-        <div class="assist-docs-topics" data-bind="css: { 'assist-flex-fill': !selectedTopic(), 'assist-flex-40': selectedTopic() }">
-          <!-- ko ifnot: query -->
-          <!-- ko template: { name: 'language-reference-topic-tree', data: topics } --><!-- /ko -->
-          <!-- /ko -->
-          <!-- ko if: query -->
-          <!-- ko if: filteredTopics().length > 0 -->
-          <ul class="assist-docs-topic-tree" data-bind="foreach: filteredTopics">
-            <li>
-              <a class="assist-field-link" href="javascript: void(0);" data-bind="css: { 'blue': $component.selectedTopic() === $data }, click: function () { $component.selectedTopic($data); }, html: titleMatch() || title"></a>
-            </li>
-          </ul>
-          <!-- /ko -->
-          <!-- ko if: filteredTopics().length === 0 -->
-          <ul class="assist-docs-topic-tree">
-            <li class="assist-no-entries">${ _('No matches found. ') }</li>
-          </ul>
-          <!-- /ko -->
-          <!-- /ko -->
-        </div>
-        <!-- ko if: selectedTopic -->
-        <div class="assist-flex-60 assist-docs-details" data-bind="with: selectedTopic">
-          <div class="assist-panel-close"><button class="close" data-bind="click: function() { $component.selectedTopic(undefined); }">&times;</button></div>
-          <div data-bind="html: bodyMatch() || body()"></div>
-        </div>
-        <!-- /ko -->
-      </div>
-    </div>
-  </script>
-
-  <script type="text/html" id="functions-panel-template">
-    <div class="assist-inner-panel">
-      <div class="assist-flex-panel">
-        <div class="assist-flex-header">
-          <div class="assist-inner-header">
-            <div class="function-dialect-dropdown" data-bind="component: { name: 'hue-drop-down', params: { fixedPosition: true, value: activeType, entries: availableTypes, linkTitle: '${ _ko('Selected dialect') }' } }" style="display: inline-block"></div>
-          </div>
-        </div>
-        <div class="assist-flex-search">
-          <div class="assist-filter">
-            <input class="clearable" type="text" placeholder="Filter..." data-bind="clearable: query, value: query, valueUpdate: 'afterkeydown'">
-          </div>
-        </div>
-        <div data-bind="css: { 'assist-flex-fill': !selectedFunction(), 'assist-flex-half': selectedFunction() }">
-          <!-- ko ifnot: query -->
-          <ul class="assist-function-categories" data-bind="foreach: activeCategories">
-            <li>
-              <a class="black-link" href="javascript: void(0);" data-bind="toggle: open"><i class="fa fa-fw" data-bind="css: { 'fa-chevron-right': !open(), 'fa-chevron-down': open }"></i> <span data-bind="text: name"></span></a>
-              <ul class="assist-functions" data-bind="slideVisible: open, foreach: functions">
-                <li data-bind="tooltip: { title: description, placement: 'left', delay: 1000 }">
-                  <a class="assist-field-link" href="javascript: void(0);" data-bind="draggableText: { text: draggable, meta: { type: 'function' } }, css: { 'blue': $parents[1].selectedFunction() === $data }, multiClick: { click: function () { $parents[1].selectedFunction($data); }, dblClick: function () { huePubSub.publish('editor.insert.at.cursor', draggable); } }, text: signature"></a>
-                </li>
-              </ul>
-            </li>
-          </ul>
-          <!-- /ko -->
-          <!-- ko if: query -->
-          <!-- ko if: filteredFunctions().length > 0 -->
-          <ul class="assist-functions" data-bind="foreach: filteredFunctions">
-            <li data-bind="tooltip: { title: description, placement: 'left', delay: 1000 }">
-              <a class="assist-field-link" href="javascript: void(0);" data-bind="draggableText: { text: draggable, meta: { type: 'function' } }, css: { 'blue': $parent.selectedFunction() === $data }, multiClick: { click: function () { $parent.selectedFunction($data); }, dblClick: function () { huePubSub.publish('editor.insert.at.cursor', draggable); } }, html: signatureMatch"></a>
-            </li>
-          </ul>
-          <!-- /ko -->
-          <!-- ko if: filteredFunctions().length === 0 -->
-          <ul class="assist-functions">
-            <li class="assist-no-entries">${ _('No functions found. ') }</li>
-          </ul>
-          <!-- /ko -->
-          <!-- /ko -->
-        </div>
-        <!-- ko if: selectedFunction -->
-        <div class="assist-flex-half assist-function-details" data-bind="with: selectedFunction">
-          <div class="assist-panel-close"><button class="close" data-bind="click: function() { $parent.selectedFunction(null); }">&times;</button></div>
-          <div class="assist-function-signature blue" data-bind="draggableText: { text: draggable, meta: { type: 'function' } }, text: signature, event: { 'dblclick': function () { huePubSub.publish('editor.insert.at.cursor', draggable); } }"></div>
-          <!-- ko if: description -->
-          <div data-bind="html: descriptionMatch"></div>
-          <!-- /ko -->
-        </div>
-        <!-- /ko -->
-      </div>
-    </div>
-  </script>
-
-  <script type="text/javascript">
-    (function () {
-
-      ${ sqlDocIndex.sqlDocIndex() }
-      ${ sqlDocIndex.sqlDocTopLevel() }
-
-      var LanguageReferenceTopic = function (entry, index) {
-        var self = this;
-        self.ref = entry.ref;
-        self.title = entry.title;
-        self.index = index;
-        self.weight = 1;
-        self.children = [];
-        entry.children.forEach(function (child) {
-          self.children.push(new LanguageReferenceTopic(child, self.index));
-        });
-        self.loadDeferred = $.Deferred();
-        self.loading = ko.observable(false);
-        self.body = ko.observable();
-        self.bodyMatch = ko.observable();
-        self.open = ko.observable(false);
-        self.titleMatch = ko.observable();
-      };
-
-      LanguageReferenceTopic.prototype.load = function () {
-        var self = this;
-        if (self.body() || self.loading()) {
-          return self.loadDeferred.promise();
-        }
-        self.loading(true);
-        window.apiHelper.simpleGet(self.index[self.ref]).done(function (doc) {
-          self.body(doc.body);
-        }).always(function () {
-          self.loading(false);
-          self.loadDeferred.resolve(self);
-        });
-        return self.loadDeferred.promise();
-      };
-
-      function LanguageReferencePanel (params, element) {
-        var self = this;
-        self.disposals = [];
-
-        self.availableTypes = ['impala', 'hive'];
-
-        self.sourceType = ko.observable('hive');
-
-        self.allTopics = {
-          impala: [],
-          hive: []
-        };
-        window.IMPALA_DOC_TOP_LEVEL.forEach(function (topLevelItem) {
-          self.allTopics.impala.push(new LanguageReferenceTopic(topLevelItem, window.IMPALA_DOC_INDEX));
-        });
-        window.HIVE_DOC_TOP_LEVEL.forEach(function (topLevelItem) {
-          self.allTopics.hive.push(new LanguageReferenceTopic(topLevelItem, window.HIVE_DOC_INDEX));
-        });
-
-        var updateType = function (type) {
-          if (self.availableTypes.indexOf(type) !== -1) {
-            self.sourceType(type);
-          }
-        };
-
-        var activeSnippetTypeSub = huePubSub.subscribe('active.snippet.type.changed', function (details) { updateType(details.type) });
-
-        self.disposals.push(function () {
-          activeSnippetTypeSub.remove();
-        });
-
-        huePubSub.subscribeOnce('set.active.snippet.type', updateType);
-        huePubSub.publish('get.active.snippet.type');
-
-        self.topics = ko.pureComputed(function () {
-          return self.allTopics[self.sourceType()];
-        });
-
-        self.selectedTopic = ko.observable();
 
-        var selectedSub = self.selectedTopic.subscribe(function (newTopic) {
-          if (newTopic) {
-            newTopic.load();
-          }
-        });
-
-        self.disposals.push(function () {
-          selectedSub.dispose();
-        });
-        self.query = ko.observable().extend({ throttle: 200 });
-        self.filteredTopics = ko.observableArray();
-
-        var sortFilteredTopics = function () {
-          self.filteredTopics.sort(function (a, b) {
-            if (a.weight !== b.weight) {
-              return b.weight - a.weight;
-            }
-            return a.title.localeCompare(b.title);
-          });
-        };
-
-        self.query.subscribe(function (newVal) {
-          if (!newVal) {
-            return;
-          }
-          var lowerCaseQuery = self.query().toLowerCase();
-          var replaceRegexp = new RegExp('(' + lowerCaseQuery + ')', 'i');
-          self.filteredTopics([]);
-          var promises = [];
-
-          var sortTimeout = -1;
-
-          var findInside = function (topic) {
-            promises.push(topic.load().done(function (loadedTopic) {
-              var match = false;
-              var titleIndex = loadedTopic.title.toLowerCase().indexOf(lowerCaseQuery);
-              if (titleIndex !== -1) {
-                loadedTopic.weight = titleIndex === 0 ? 2 : 1;
-                loadedTopic.titleMatch(loadedTopic.title.replace(new RegExp('(' + lowerCaseQuery + ')', 'i'), '<b>$1</b>'));
-                loadedTopic.bodyMatch(undefined);
-                self.filteredTopics.push(loadedTopic);
-                match = true;
-              } else if (loadedTopic.body() && loadedTopic.body().toLowerCase().indexOf(lowerCaseQuery) !== -1) {
-                loadedTopic.weight = 0;
-                loadedTopic.titleMatch(undefined);
-                loadedTopic.bodyMatch(loadedTopic.body().replace(replaceRegexp, '<b>$1</b>'));
-                self.filteredTopics.push(loadedTopic);
-                match = true;
-              } else {
-                loadedTopic.titleMatch(undefined);
-                loadedTopic.bodyMatch(undefined);
-              }
-              if (match) {
-                window.clearTimeout(sortTimeout);
-                sortTimeout = window.setTimeout(sortFilteredTopics, 100);
-              }
-            }));
-
-            topic.children.forEach(findInside);
-          };
-
-          self.topics.forEach(findInside);
-
-          window.setTimeout(function () {
-            // Initial sort deferred for promises to complete
-            sortFilteredTopics();
-          }, 0);
-
-        });
-
-        var selectedTopicSub = self.selectedTopic.subscribe(function () {
-          $(element).find('.assist-docs-details').scrollTop(0);
-        });
-
-        var querySub = self.query.subscribe(function () {
-          $(element).find('.assist-docs-topics').scrollTop(0);
-        });
-
-        var scrollToSelectedTopic = function () {
-          var topics = $(element).find('.assist-docs-topics');
-          if (topics.find('.blue').length) {
-            topics.scrollTop(Math.min(topics.scrollTop() + topics.find('.blue').position().top - 20, topics.find('> ul').height() - topics.height()));
-          }
-        };
-
-        var scrollToAnchor = function (anchorId) {
-          if (!anchorId) {
-            return;
-          }
-          var detailsPanel = $(element).find('.assist-docs-details');
-          var found = detailsPanel.find('#' + anchorId.split('/').join(' #'));
-          if (found.length) {
-            detailsPanel.scrollTop(found.position().top - 10);
-          }
-        };
-
-        huePubSub.subscribe('scroll.test', scrollToSelectedTopic);
-
-        var showTopicSub = huePubSub.subscribe('assist.lang.ref.panel.show.topic', function (targetTopic) {
-          var topicStack = [];
-          var findTopic = function (topics) {
-            topics.some(function (topic) {
-              topicStack.push(topic);
-              if (topic.ref === targetTopic.ref) {
-                while (topicStack.length) {
-                  topicStack.pop().open(true);
-                }
-                self.query('');
-                self.selectedTopic(topic);
-                window.setTimeout(function () {
-                  scrollToAnchor(targetTopic.anchorId);
-                  scrollToSelectedTopic();
-                }, 0);
-                return true;
-              } else if (topic.children.length) {
-                var inChild = findTopic(topic.children);
-                if (inChild) {
-                  return true;
-                }
-              }
-              topicStack.pop();
-            })
-          };
-          findTopic(self.topics());
-        });
-
-        $(element).on('click.langref', function (event) {
-          if (event.target.className === 'hue-doc-internal-link') {
-            huePubSub.publish('assist.lang.ref.panel.show.topic', {
-              ref: $(event.target).data('doc-ref'),
-              anchorId: $(event.target).data('doc-anchor-id')
-            });
-          }
-        });
-
-        self.disposals.push(function () {
-          selectedTopicSub.dispose();
-          querySub.dispose();
-          showTopicSub.remove();
-          $(element).off('click.langref');
-        });
-      }
-
-      LanguageReferencePanel.prototype.dispose = function () {
-        var self = this;
-        while (self.disposals.length) {
-          self.disposals.pop()();
-        }
-      };
-
-      ko.components.register('language-reference-panel', {
-        viewModel: {
-          createViewModel: function(params, componentInfo) {
-            return new LanguageReferencePanel(params, componentInfo.element)
-          }
-        },
-        template: { element: 'language-reference-panel-template' }
-      });
-
-      function FunctionsPanel(params) {
-        var self = this;
-        self.categories = {};
-        self.disposals = [];
-
-        self.activeType = ko.observable();
-        self.availableTypes = ko.observableArray(window.IS_EMBEDDED ? ['Impala'] : ['Hive', 'Impala', 'Pig']);
-        self.query = ko.observable().extend({ rateLimit: 400 });
-        self.selectedFunction = ko.observable();
-
-        self.availableTypes().forEach(function (type) {
-          self.initFunctions(type);
-        });
-
-        var selectedFunctionPerType = { 'Hive': null, 'Impala': null, 'Pig': null };
-        self.selectedFunction.subscribe(function (newFunction) {
-          if (newFunction) {
-            selectedFunctionPerType[self.activeType()] = newFunction;
-            if (!newFunction.category.open()) {
-              newFunction.category.open(true);
-            }
-          }
-        });
-
-        self.activeCategories = ko.observableArray();
-
-        self.filteredFunctions = ko.pureComputed(function () {
-          var result = [];
-          var lowerCaseQuery = self.query().toLowerCase();
-          var replaceRegexp = new RegExp('(' + lowerCaseQuery + ')', 'i');
-          self.activeCategories().forEach(function (category) {
-            category.functions.forEach(function (fn) {
-              if (fn.signature.toLowerCase().indexOf(lowerCaseQuery) === 0) {
-                fn.weight = 2;
-                fn.signatureMatch(fn.signature.replace(replaceRegexp, '<b>$1</b>'));
-                fn.descriptionMatch(fn.description);
-                result.push(fn);
-              } else if (fn.signature.toLowerCase().indexOf(lowerCaseQuery) !== -1) {
-                fn.weight = 1;
-                fn.signatureMatch(fn.signature.replace(replaceRegexp, '<b>$1</b>'));
-                fn.descriptionMatch(fn.description);
-                result.push(fn);
-              } else if ((fn.description && fn.description.toLowerCase().indexOf(lowerCaseQuery) !== -1)) {
-                fn.signatureMatch(fn.signature);
-                fn.descriptionMatch(fn.description.replace(replaceRegexp, '<b>$1</b>'));
-                fn.weight = 0;
-                result.push(fn);
-              } else {
-                if (fn.signatureMatch() !== fn.signature) {
-                  fn.signatureMatch(fn.signature);
-                }
-                if (fn.descriptionMatch() !== fn.desciption) {
-                  fn.descriptionMatch(fn.description);
-                }
-              }
-            });
-          });
-          result.sort(function (a, b) {
-            if (a.weight !== b.weight) {
-              return b.weight - a.weight;
-            }
-            return a.signature.localeCompare(b.signature);
-          });
-          return result;
-        });
-
-        self.activeType.subscribe(function (newType) {
-          self.selectedFunction(selectedFunctionPerType[newType]);
-          self.activeCategories(self.categories[newType]);
-          window.apiHelper.setInTotalStorage('assist', 'function.panel.active.type', newType);
-        });
-
-        var lastActiveType = window.apiHelper.getFromTotalStorage('assist', 'function.panel.active.type', self.availableTypes()[0]);
-        self.activeType(lastActiveType);
-
-        var updateType = function (type) {
-          self.availableTypes().every(function (availableType) {
-            if (availableType.toLowerCase() === type) {
-              if (self.activeType() !== availableType) {
-                self.activeType(availableType);
-              }
-              return false;
-            }
-            return true;
-          });
-        };
-
-        var activeSnippetTypeSub = huePubSub.subscribe('active.snippet.type.changed', function (details) { updateType(details.type) });
-
-        self.disposals.push(function () {
-          activeSnippetTypeSub.remove();
-        });
-
-        huePubSub.subscribeOnce('set.active.snippet.type', updateType);
-        huePubSub.publish('get.active.snippet.type');
-      }
-
-      FunctionsPanel.prototype.dispose = function () {
-        var self = this;
-        self.disposals.forEach(function (dispose) {
-          dispose();
-        })
-      };
-
-      FunctionsPanel.prototype.initFunctions = function (dialect) {
-        var self = this;
-        self.categories[dialect] = [];
-        var functions = dialect === 'Pig' ? PigFunctions.CATEGORIZED_FUNCTIONS : SqlFunctions.CATEGORIZED_FUNCTIONS[dialect.toLowerCase()];
-
-        functions.forEach(function (category) {
-          var koCategory = {
-            name: category.name,
-            open: ko.observable(false),
-            functions: $.map(category.functions, function(fn) {
-              return {
-                draggable: fn.draggable,
-                signature: fn.signature,
-                signatureMatch: ko.observable(fn.signature),
-                description: fn.description,
-                descriptionMatch: ko.observable(fn.description)
-              }
-            })
-          };
-          koCategory.functions.forEach(function (fn) {
-            fn.category = koCategory;
-          });
-          self.categories[dialect].push(koCategory)
-        });
-      };
+<%def name="assistPanel(is_s3_enabled=False)">
 
-      ko.components.register('functions-panel', {
-        viewModel: FunctionsPanel,
-        template: { element: 'functions-panel-template' }
-      });
-    })();
-  </script>
 
   <script type="text/html" id="editor-assistant-panel-template">
     <div class="assist-inner-panel assist-assistant-panel">

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

@@ -359,6 +359,7 @@
     'Search data and saved documents...': '${ _('Search data and saved documents...') }',
     'Search saved documents...': '${_('Search saved documents...')}',
     'Select this folder': '${_('Select this folder')}',
+    'Selected dialect': '${_('Selected dialect')}',
     'Selected entry': '${_('Selected entry')}',
     'Sentry will recursively delete the SERVER or DATABASE privileges you marked for deletion.': '${ _('Sentry will recursively delete the SERVER or DATABASE privileges you marked for deletion.') }',
     'Server': '${ _('Server') }',
@@ -504,4 +505,5 @@
   };
 
   ${ sqlDocIndex.sqlDocIndex() }
+  ${ sqlDocIndex.sqlDocTopLevel() }
 })();