浏览代码

[editor] Move the SQL Syntax Dropdown from mako to js to get rid of inline script (#3653)

No code changes other than linting and some small clean-up.
Johan Åhlén 1 年之前
父节点
当前提交
b6b2eccc90

+ 51 - 0
desktop/core/src/desktop/js/apps/editor/components/__snapshots__/ko.syntaxDropdown.test.js.snap

@@ -0,0 +1,51 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ko.syntaxDropdown.js should match snapshot 1`] = `
+"<html><head></head><body><div id=\\"sqlSyntaxDropdown\\" data-bind=\\"component: { name: 'sql-syntax-dropdown', params: $data }\\">
+<div class=\\"hue-syntax-drop-down\\" data-bind=\\"
+  style: {
+    'left': left() + 'px',
+    'top': top() + 'px'
+  },
+  component: {
+    name: 'hue-drop-down',
+    params: {
+      value: selected,
+      entries: expected,
+      foreachVisible: false,
+      searchable: false,
+      showOnInit: true,
+      menuOnly: true
+    }
+  }
+\\" style=\\"left: 10px; top: 20px;\\">
+  <!-- ko if: !menuOnly && (!dropDownVisible() || !searchable) --><!-- /ko -->
+  <!-- ko if: !menuOnly && (dropDownVisible() && searchable) --><!-- /ko -->
+  <div class=\\"hue-drop-down-container open\\" data-bind=\\"css: { 'open' : dropDownVisible, 'hue-drop-down-fixed': fixedPosition, 'hue-drop-down-container-searchable': searchable }, dropDownKeyUp: { onEsc: onEsc, onEnter: onEnter, dropDownVisible: dropDownVisible }\\">
+    <div style=\\"overflow-y: auto;\\" class=\\"dropdown-menu\\" data-bind=\\"visible: filteredEntries().length > 0\\">
+      <!-- ko if: foreachVisible --><!-- /ko -->
+      <!-- ko ifnot: foreachVisible -->
+      <ul class=\\"hue-inner-drop-down\\" data-bind=\\"foreach: filteredEntries\\">
+        <!-- ko if: typeof $data.divider !== 'undefined' && $data.divider --><!-- /ko -->
+        <!-- ko if: typeof $data.divider === 'undefined' || !$data.divider -->
+        <li><a href=\\"javascript:void(0)\\" data-bind=\\"text: $data &amp;&amp; typeof $data[$parent.labelAttribute] !== 'undefined' ? $data[$parent.labelAttribute] : $data, click: function () { let previous = $parent.value(); $parent.value($data); $parent.onSelect($data, previous); }\\">foo</a></li>
+        <!-- /ko -->
+      
+        <!-- ko if: typeof $data.divider !== 'undefined' && $data.divider -->
+        <li class=\\"divider\\"></li>
+        <!-- /ko -->
+        <!-- ko if: typeof $data.divider === 'undefined' || !$data.divider --><!-- /ko -->
+      
+        <!-- ko if: typeof $data.divider !== 'undefined' && $data.divider --><!-- /ko -->
+        <!-- ko if: typeof $data.divider === 'undefined' || !$data.divider -->
+        <li><a href=\\"javascript:void(0)\\" data-bind=\\"text: $data &amp;&amp; typeof $data[$parent.labelAttribute] !== 'undefined' ? $data[$parent.labelAttribute] : $data, click: function () { let previous = $parent.value(); $parent.value($data); $parent.onSelect($data, previous); }\\">Ignore this type of error</a></li>
+        <!-- /ko -->
+      </ul>
+      <!-- /ko -->
+    </div>
+  </div>
+</div>
+</div></body></html>"
+`;
+
+exports[`ko.syntaxDropdown.js should render component on show event 1`] = `"<html><head></head><body><div id=\\"sqlSyntaxDropdown\\" data-bind=\\"component: { name: 'sql-syntax-dropdown', params: $data }\\"></div></body></html>"`;

+ 147 - 0
desktop/core/src/desktop/js/apps/editor/components/ko.syntaxDropdown.js

@@ -0,0 +1,147 @@
+// 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 * as ko from 'knockout';
+import $ from 'jquery';
+
+import 'ko/components/ko.dropDown';
+import componentUtils from 'ko/components/componentUtils';
+import DisposableComponent from 'ko/components/DisposableComponent';
+import huePubSub from 'utils/huePubSub';
+import I18n from 'utils/i18n';
+import { hueLocalStorage } from 'utils/storageUtils';
+
+export const SYNTAX_DROPDOWN_COMPONENT = 'sql-syntax-dropdown';
+export const SYNTAX_DROPDOWN_ID = 'sqlSyntaxDropdown';
+export const SHOW_EVENT = 'sql.syntax.dropdown.show';
+export const SHOWN_EVENT = 'sql.syntax.dropdown.shown';
+export const HIDE_EVENT = 'sql.syntax.dropdown.hide';
+
+// prettier-ignore
+const TEMPLATE = `
+<div class="hue-syntax-drop-down" data-bind="
+  style: {
+    'left': left() + 'px',
+    'top': top() + 'px'
+  },
+  component: {
+    name: 'hue-drop-down',
+    params: {
+      value: selected,
+      entries: expected,
+      foreachVisible: false,
+      searchable: false,
+      showOnInit: true,
+      menuOnly: true
+    }
+  }
+"></div>
+`;
+
+const hideSyntaxDropdown = () => {
+  const $sqlSyntaxDropdown = $(`#${SYNTAX_DROPDOWN_ID}`);
+  if ($sqlSyntaxDropdown.length) {
+    ko.cleanNode($sqlSyntaxDropdown[0]);
+    $sqlSyntaxDropdown.remove();
+    $(document).off('click', hideOnClickOutside);
+  }
+};
+
+const hideOnClickOutside = event => {
+  const $modal = $('.modal');
+  if (
+    $.contains(document, event.target) &&
+    !$.contains($(`#${SYNTAX_DROPDOWN_ID}`)[0], event.target) &&
+    ($modal[0].length === 0 || !$.contains($modal[0], event.target))
+  ) {
+    hideSyntaxDropdown();
+  }
+};
+
+class SqlSyntaxDropdownViewModel extends DisposableComponent {
+  constructor(params) {
+    super();
+
+    this.selected = ko.observable();
+
+    const expected = params.data.expected.map(expected => expected.text);
+    // TODO: Allow suppression of unknown columns etc.
+    if (params.data.ruleId) {
+      if (expected.length > 0) {
+        expected.push({
+          divider: true
+        });
+      }
+      expected.push({
+        label: I18n('Ignore this type of error'),
+        suppressRule: params.data.ruleId.toString() + params.data.text.toLowerCase()
+      });
+    }
+    this.expected = ko.observableArray(expected);
+
+    const selectedSub = this.selected.subscribe(newValue => {
+      if (typeof newValue.suppressRule !== 'undefined') {
+        const suppressedRules = hueLocalStorage('hue.syntax.checker.suppressedRules') || {};
+        suppressedRules[newValue.suppressRule] = true;
+        hueLocalStorage('hue.syntax.checker.suppressedRules', suppressedRules);
+        huePubSub.publish('editor.refresh.statement.locations', params.editorId);
+      } else {
+        params.editor.session.replace(params.range, newValue);
+      }
+      hideSyntaxDropdown();
+    });
+
+    this.addDisposalCallback(() => {
+      selectedSub.dispose();
+    });
+
+    this.left = ko.observable(params.source.left);
+    this.top = ko.observable(params.source.bottom);
+
+    const closeOnEsc = e => {
+      if (e.keyCode === 27) {
+        hideSyntaxDropdown();
+      }
+    };
+
+    $(document).on('keyup', closeOnEsc);
+
+    this.addDisposalCallback(() => {
+      $(document).off('keyup', closeOnEsc);
+    });
+
+    window.setTimeout(() => {
+      $(document).on('click', hideOnClickOutside);
+    }, 0);
+
+    this.addDisposalCallback(() => {
+      $(document).off('click', hideOnClickOutside);
+    });
+  }
+}
+
+componentUtils.registerComponent(SYNTAX_DROPDOWN_COMPONENT, SqlSyntaxDropdownViewModel, TEMPLATE);
+
+huePubSub.subscribe(HIDE_EVENT, hideSyntaxDropdown);
+huePubSub.subscribe(SHOW_EVENT, details => {
+  hideSyntaxDropdown();
+  const $sqlSyntaxDropdown = $(
+    `<div id="${SYNTAX_DROPDOWN_ID}" data-bind="component: { name: '${SYNTAX_DROPDOWN_COMPONENT}', params: $data }"></div>`
+  );
+  $('body').append($sqlSyntaxDropdown);
+  ko.applyBindings(details, $sqlSyntaxDropdown[0]);
+  huePubSub.publish(SHOWN_EVENT);
+});

+ 54 - 0
desktop/core/src/desktop/js/apps/editor/components/ko.syntaxDropdown.test.js

@@ -0,0 +1,54 @@
+// 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 { SHOW_EVENT, SHOWN_EVENT, SYNTAX_DROPDOWN_ID } from './ko.syntaxDropdown';
+import huePubSub from 'utils/huePubSub';
+
+describe('ko.syntaxDropdown.js', () => {
+  const publishShowEvent = () => {
+    huePubSub.publish(SHOW_EVENT, {
+      data: {
+        text: 'floo',
+        expected: [{ text: 'foo' }],
+        ruleId: 123
+      },
+      source: {
+        left: 10,
+        bottom: 20
+      }
+    });
+  };
+
+  it('should render component on show event', async () => {
+    expect(document.querySelectorAll(`#${SYNTAX_DROPDOWN_ID}`)).toHaveLength(0);
+
+    publishShowEvent();
+
+    expect(document.querySelectorAll(`#${SYNTAX_DROPDOWN_ID}`)).toHaveLength(1);
+    expect(window.document.documentElement.outerHTML).toMatchSnapshot();
+  });
+
+  it('should match snapshot', async () => {
+    const shownPromise = new Promise(resolve => {
+      huePubSub.subscribeOnce(SHOWN_EVENT, resolve);
+    });
+    publishShowEvent();
+
+    await shownPromise;
+
+    expect(window.document.documentElement.outerHTML).toMatchSnapshot();
+  });
+});

+ 1 - 0
desktop/core/src/desktop/js/apps/notebook/app.js

@@ -23,6 +23,7 @@ import 'ext/jquery.hotkeys';
 import 'jquery/plugins/jquery.hdfstree';
 
 import NotebookViewModel from './NotebookViewModel';
+import '../editor/components/ko.syntaxDropdown';
 import {
   ACTIVE_SNIPPET_CONNECTOR_CHANGED_EVENT,
   IGNORE_NEXT_UNLOAD_EVENT

+ 0 - 132
desktop/core/src/desktop/templates/sql_syntax_dropdown.mako

@@ -1,132 +0,0 @@
-## 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 sys
-from desktop.lib.i18n import smart_unicode
-if sys.version_info[0] > 2:
-  from django.utils.translation import gettext as _
-else:
-  from django.utils.translation import ugettext as _
-%>
-
-<%def name="sqlSyntaxDropdown()">
-  <script type="text/html" id="sql-syntax-dropdown-template">
-    <div class="hue-syntax-drop-down" data-bind="style: { 'left': left() + 'px', 'top': top() + 'px' }, component: { name: 'hue-drop-down', params: { value: selected, entries: expected, foreachVisible: false, searchable: false, showOnInit: true, menuOnly: true } }">
-    </div>
-  </script>
-
-  <script type="text/javascript">
-    (function () {
-      var hideSyntaxDropdown = function () {
-        var $sqlSyntaxDropdown = $('#sqlSyntaxDropdown');
-        if ($sqlSyntaxDropdown.length > 0) {
-          ko.cleanNode($sqlSyntaxDropdown[0]);
-          $sqlSyntaxDropdown.remove();
-          $(document).off('click', hideOnClickOutside);
-        }
-      };
-
-      var hideOnClickOutside = function (event) {
-        if (jQuery.contains(document, event.target) && !$.contains($('#sqlSyntaxDropdown')[0], event.target) && ($('.modal')[0].length === 0 || !$.contains($('.modal')[0], event.target))) {
-          hideSyntaxDropdown();
-        }
-      };
-
-      function SqlSyntaxDropdownViewModel(params) {
-        var self = this;
-        self.disposalFunctions = [];
-        self.selected = ko.observable();
-        var expected = $.map(params.data.expected, function (expected) {
-          return expected.text;
-        });
-
-        // TODO: Allow suppression of unknown columns etc.
-        if (params.data.ruleId) {
-          if (expected.length > 0) {
-            expected.push({
-              divider: true
-            });
-          }
-          expected.push({
-            label: window.I18n('Ignore this type of error'),
-            suppressRule: params.data.ruleId.toString() + params.data.text.toLowerCase()
-          });
-        }
-        self.expected = ko.observableArray(expected);
-
-        var selectedSub = self.selected.subscribe(function (newValue) {
-          if (typeof newValue.suppressRule !== 'undefined') {
-            var suppressedRules = window.hueUtils.hueLocalStorage('hue.syntax.checker.suppressedRules') || {};
-            suppressedRules[newValue.suppressRule] = true;
-            window.hueUtils.hueLocalStorage('hue.syntax.checker.suppressedRules', suppressedRules);
-            huePubSub.publish('editor.refresh.statement.locations', params.editorId);
-          } else {
-            params.editor.session.replace(params.range, newValue);
-          }
-          hideSyntaxDropdown();
-        });
-        self.disposalFunctions.push(function () {
-          selectedSub.dispose();
-        });
-
-        self.left = ko.observable(params.source.left);
-        self.top = ko.observable(params.source.bottom);
-
-        var closeOnEsc = function (e) {
-          if (e.keyCode === 27) {
-            hideSyntaxDropdown();
-          }
-        };
-
-        $(document).on('keyup', closeOnEsc);
-
-        self.disposalFunctions.push(function () {
-          $(document).off('keyup', closeOnEsc);
-        });
-
-        window.setTimeout(function() {
-          $(document).on('click', hideOnClickOutside);
-        }, 0);
-
-        self.disposalFunctions.push(function () {
-          $(document).off('click', hideOnClickOutside);
-        })
-      }
-
-      SqlSyntaxDropdownViewModel.prototype.dispose = function() {
-        var self = this;
-        self.disposalFunctions.forEach(function (fn) {
-          fn();
-        })
-      };
-
-      ko.components.register('sql-syntax-dropdown', {
-        viewModel: SqlSyntaxDropdownViewModel,
-        template: { element: 'sql-syntax-dropdown-template' }
-      });
-
-      huePubSub.subscribe('sql.syntax.dropdown.hide', hideSyntaxDropdown);
-
-      huePubSub.subscribe('sql.syntax.dropdown.show', function (details) {
-        hideSyntaxDropdown();
-        var $sqlSyntaxDropdown = $('<div id="sqlSyntaxDropdown" data-bind="component: { name: \'sql-syntax-dropdown\', params: $data }"></div>');
-        $('body').append($sqlSyntaxDropdown);
-        ko.applyBindings(details, $sqlSyntaxDropdown[0]);
-      });
-    })();
-  </script>
-</%def>

+ 0 - 3
desktop/libs/notebook/src/notebook/templates/editor_components.mako

@@ -103,7 +103,6 @@ else:
 <![endif]-->
 
 <%namespace name="dashboard" file="/common_dashboard.mako" />
-<%namespace name="sqlSyntaxDropdown" file="/sql_syntax_dropdown.mako" />
 
 </%def>
 
@@ -130,8 +129,6 @@ else:
 % endif
 </style>
 
-${ sqlSyntaxDropdown.sqlSyntaxDropdown() }
-
 <div class="navbar hue-title-bar" data-bind="visible: ! $root.isPresentationMode() && ! $root.isResultFullScreenMode()">
   <div class="navbar-inner">
     <div class="container-fluid">