|
@@ -19,9 +19,10 @@ import * as ko from 'knockout';
|
|
|
import apiHelper from 'api/apiHelper';
|
|
import apiHelper from 'api/apiHelper';
|
|
|
import componentUtils from 'ko/components/componentUtils';
|
|
import componentUtils from 'ko/components/componentUtils';
|
|
|
import huePubSub from 'utils/huePubSub';
|
|
import huePubSub from 'utils/huePubSub';
|
|
|
-import { PigFunctions, SqlFunctions } from 'sql/sqlFunctions';
|
|
|
|
|
import I18n from 'utils/i18n';
|
|
import I18n from 'utils/i18n';
|
|
|
import { CONFIG_REFRESHED_EVENT, filterEditorConnectors } from 'utils/hueConfig';
|
|
import { CONFIG_REFRESHED_EVENT, filterEditorConnectors } from 'utils/hueConfig';
|
|
|
|
|
+import { getUdfCategories, hasUdfCategories } from 'sql/reference/sqlReferenceRepository';
|
|
|
|
|
+import { HUE_DROP_DOWN_COMPONENT } from 'ko/components/ko.dropDown';
|
|
|
|
|
|
|
|
export const NAME = 'assist-functions-panel';
|
|
export const NAME = 'assist-functions-panel';
|
|
|
// prettier-ignore
|
|
// prettier-ignore
|
|
@@ -30,89 +31,98 @@ const TEMPLATE = `
|
|
|
<div class="assist-flex-panel">
|
|
<div class="assist-flex-panel">
|
|
|
<div class="assist-flex-header">
|
|
<div class="assist-flex-header">
|
|
|
<div class="assist-inner-header">
|
|
<div class="assist-inner-header">
|
|
|
- <div class="function-dialect-dropdown" data-bind="component: { name: 'hue-drop-down', params: { fixedPosition: true, value: activeDialect, entries: availableDialects, linkTitle: '${I18n(
|
|
|
|
|
- 'Selected dialect'
|
|
|
|
|
- )}' } }" style="display: inline-block"></div>
|
|
|
|
|
|
|
+ <div class="function-dialect-dropdown" data-bind="
|
|
|
|
|
+ component: {
|
|
|
|
|
+ name: '${ HUE_DROP_DOWN_COMPONENT }',
|
|
|
|
|
+ params: {
|
|
|
|
|
+ fixedPosition: true,
|
|
|
|
|
+ value: activeConnectorUdfs,
|
|
|
|
|
+ entries: availableConnectorUdfs,
|
|
|
|
|
+ linkTitle: '${I18n('Selected type')}'
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ " style="display: inline-block"></div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="assist-flex-search">
|
|
|
|
|
- <div class="assist-filter">
|
|
|
|
|
- <form autocomplete="off">
|
|
|
|
|
- <input class="clearable" type="text" ${ window.PREVENT_AUTOFILL_INPUT_ATTRS } placeholder="Filter..." data-bind="clearable: query, value: query, valueUpdate: 'afterkeydown'">
|
|
|
|
|
- </form>
|
|
|
|
|
- </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', { text: 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', { text: draggable }); } }, html: signatureMatch"></a>
|
|
|
|
|
- </li>
|
|
|
|
|
- </ul>
|
|
|
|
|
- <!-- /ko -->
|
|
|
|
|
- <!-- ko if: filteredFunctions().length === 0 -->
|
|
|
|
|
- <ul class="assist-functions">
|
|
|
|
|
- <li class="assist-no-entries">${I18n('No results found.')}</li>
|
|
|
|
|
- </ul>
|
|
|
|
|
- <!-- /ko -->
|
|
|
|
|
|
|
+ <!-- ko with: activeConnectorUdfs -->
|
|
|
|
|
+ <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
|
|
|
|
|
+ <!-- ko ifnot: loading -->
|
|
|
|
|
+ <div class="assist-flex-search">
|
|
|
|
|
+ <div class="assist-filter">
|
|
|
|
|
+ <form autocomplete="off">
|
|
|
|
|
+ <input class="clearable" type="text" ${ window.PREVENT_AUTOFILL_INPUT_ATTRS } placeholder="Filter..." data-bind="clearable: query, value: query, valueUpdate: 'afterkeydown'">
|
|
|
|
|
+ </form>
|
|
|
|
|
+ </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: categories">
|
|
|
|
|
+ <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', { text: 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', { text: draggable }); } }, html: signatureMatch"></a>
|
|
|
|
|
+ </li>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ <!-- /ko -->
|
|
|
|
|
+ <!-- ko if: filteredFunctions().length === 0 -->
|
|
|
|
|
+ <ul class="assist-functions">
|
|
|
|
|
+ <li class="assist-no-entries">${I18n('No results 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); }">×</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', { text: draggable }); } }"></div>
|
|
|
|
|
+ <!-- ko if: description -->
|
|
|
|
|
+ <div data-bind="html: descriptionMatch"></div>
|
|
|
|
|
+ <!-- /ko -->
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- /ko -->
|
|
|
<!-- /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); }">×</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', { text: draggable }); } }"></div>
|
|
|
|
|
- <!-- ko if: description -->
|
|
|
|
|
- <div data-bind="html: descriptionMatch"></div>
|
|
|
|
|
- <!-- /ko -->
|
|
|
|
|
- </div>
|
|
|
|
|
<!-- /ko -->
|
|
<!-- /ko -->
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
`;
|
|
`;
|
|
|
|
|
|
|
|
-class AssistFunctionsPanel {
|
|
|
|
|
- constructor(params) {
|
|
|
|
|
- this.categories = {};
|
|
|
|
|
-
|
|
|
|
|
- this.connector = params.connector;
|
|
|
|
|
|
|
+class ConnectorUdfCategories {
|
|
|
|
|
+ constructor(connector) {
|
|
|
|
|
+ this.connector = connector;
|
|
|
|
|
+ this.loading = ko.observable(true);
|
|
|
|
|
+ this.label = this.connector.displayName;
|
|
|
|
|
|
|
|
- this.activeDialect = ko.observable();
|
|
|
|
|
- this.availableDialects = ko.observableArray();
|
|
|
|
|
|
|
+ this.categories = [];
|
|
|
|
|
|
|
|
this.query = ko.observable().extend({ rateLimit: 400 });
|
|
this.query = ko.observable().extend({ rateLimit: 400 });
|
|
|
- this.selectedFunction = ko.observable();
|
|
|
|
|
|
|
|
|
|
- const selectedFunctionPeDialect = {};
|
|
|
|
|
|
|
+ this.selectedFunction = ko.observable();
|
|
|
|
|
|
|
|
this.selectedFunction.subscribe(newFunction => {
|
|
this.selectedFunction.subscribe(newFunction => {
|
|
|
- if (newFunction) {
|
|
|
|
|
- selectedFunctionPeDialect[this.activeDialect()] = newFunction;
|
|
|
|
|
- if (!newFunction.category.open()) {
|
|
|
|
|
- newFunction.category.open(true);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (newFunction && !newFunction.category.open()) {
|
|
|
|
|
+ newFunction.category.open(true);
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- this.activeCategories = ko.observableArray();
|
|
|
|
|
-
|
|
|
|
|
this.filteredFunctions = ko.pureComputed(() => {
|
|
this.filteredFunctions = ko.pureComputed(() => {
|
|
|
const result = [];
|
|
const result = [];
|
|
|
|
|
+ if (this.loading()) {
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
const lowerCaseQuery = this.query().toLowerCase();
|
|
const lowerCaseQuery = this.query().toLowerCase();
|
|
|
const replaceRegexp = new RegExp('(' + lowerCaseQuery + ')', 'i');
|
|
const replaceRegexp = new RegExp('(' + lowerCaseQuery + ')', 'i');
|
|
|
- this.activeCategories().forEach(category => {
|
|
|
|
|
|
|
+ this.categories.forEach(category => {
|
|
|
category.functions.forEach(fn => {
|
|
category.functions.forEach(fn => {
|
|
|
if (fn.signature.toLowerCase().indexOf(lowerCaseQuery) === 0) {
|
|
if (fn.signature.toLowerCase().indexOf(lowerCaseQuery) === 0) {
|
|
|
fn.weight = 2;
|
|
fn.weight = 2;
|
|
@@ -151,81 +161,13 @@ class AssistFunctionsPanel {
|
|
|
return result;
|
|
return result;
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- this.activeDialect.subscribe(newDialect => {
|
|
|
|
|
- if (newDialect) {
|
|
|
|
|
- this.selectedFunction(selectedFunctionPeDialect[newDialect]);
|
|
|
|
|
- this.activeCategories(this.categories[newDialect]);
|
|
|
|
|
- apiHelper.setInTotalStorage('assist', 'function.panel.active.dialect', newDialect);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ this.fetchUdfs().then(() => {
|
|
|
|
|
+ this.loading(false);
|
|
|
});
|
|
});
|
|
|
-
|
|
|
|
|
- const updateDialect = dialect => {
|
|
|
|
|
- this.availableDialects().every(availableDialect => {
|
|
|
|
|
- if (availableDialect.toLowerCase() === dialect) {
|
|
|
|
|
- if (this.activeDialect() !== availableDialect) {
|
|
|
|
|
- this.activeDialect(availableDialect);
|
|
|
|
|
- }
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- return true;
|
|
|
|
|
- });
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- this.connector.subscribe(connector => {
|
|
|
|
|
- if (connector) {
|
|
|
|
|
- updateDialect(connector.dialect);
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- const configUpdated = () => {
|
|
|
|
|
- const lastActiveDialect =
|
|
|
|
|
- this.activeDialect() ||
|
|
|
|
|
- apiHelper.getFromTotalStorage('assist', 'function.panel.active.dialect');
|
|
|
|
|
-
|
|
|
|
|
- const uniqueDialects = {};
|
|
|
|
|
-
|
|
|
|
|
- const configuredDialects = filterEditorConnectors(connector => {
|
|
|
|
|
- const isMatch =
|
|
|
|
|
- !uniqueDialects[connector.dialect] &&
|
|
|
|
|
- (connector.dialect === 'hive' ||
|
|
|
|
|
- connector.dialect === 'impala' ||
|
|
|
|
|
- connector.dialect === 'pig');
|
|
|
|
|
- uniqueDialects[connector.dialect] = true;
|
|
|
|
|
- return isMatch;
|
|
|
|
|
- }).map(connector => connector.dialect);
|
|
|
|
|
- configuredDialects.sort();
|
|
|
|
|
- this.availableDialects(configuredDialects);
|
|
|
|
|
-
|
|
|
|
|
- this.availableDialects().forEach(dialect => {
|
|
|
|
|
- this.initFunctions(dialect);
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- if (
|
|
|
|
|
- lastActiveDialect &&
|
|
|
|
|
- this.availableDialects().find(dialect => dialect === lastActiveDialect)
|
|
|
|
|
- ) {
|
|
|
|
|
- this.activeDialect(lastActiveDialect);
|
|
|
|
|
- } else {
|
|
|
|
|
- this.activeDialect(
|
|
|
|
|
- this.availableDialects().length ? this.availableDialects()[0] : undefined
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
- if (this.connector()) {
|
|
|
|
|
- updateDialect(this.connector().dialect);
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- configUpdated();
|
|
|
|
|
- huePubSub.subscribe(CONFIG_REFRESHED_EVENT, configUpdated);
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- initFunctions(dialect) {
|
|
|
|
|
- this.categories[dialect] = [];
|
|
|
|
|
- const functions =
|
|
|
|
|
- dialect === 'pig'
|
|
|
|
|
- ? PigFunctions.CATEGORIZED_FUNCTIONS
|
|
|
|
|
- : SqlFunctions.CATEGORIZED_FUNCTIONS[dialect.toLowerCase()];
|
|
|
|
|
-
|
|
|
|
|
|
|
+ async fetchUdfs() {
|
|
|
|
|
+ const functions = await getUdfCategories(this.connector);
|
|
|
functions.forEach(category => {
|
|
functions.forEach(category => {
|
|
|
const koCategory = {
|
|
const koCategory = {
|
|
|
name: category.name,
|
|
name: category.name,
|
|
@@ -244,11 +186,80 @@ class AssistFunctionsPanel {
|
|
|
koCategory.functions.forEach(fn => {
|
|
koCategory.functions.forEach(fn => {
|
|
|
fn.category = koCategory;
|
|
fn.category = koCategory;
|
|
|
});
|
|
});
|
|
|
- this.categories[dialect].push(koCategory);
|
|
|
|
|
|
|
+ this.categories.push(koCategory);
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+class AssistFunctionsPanel {
|
|
|
|
|
+ constructor(params) {
|
|
|
|
|
+ this.categories = {};
|
|
|
|
|
+
|
|
|
|
|
+ this.activeConnector = params.activeConnector;
|
|
|
|
|
+
|
|
|
|
|
+ this.availableConnectorUdfs = ko.observableArray();
|
|
|
|
|
+ this.activeConnectorUdfs = ko.observable();
|
|
|
|
|
+
|
|
|
|
|
+ this.activeConnectorUdfs.subscribe(activeConnectorUdfs => {
|
|
|
|
|
+ apiHelper.setInTotalStorage(
|
|
|
|
|
+ 'assist',
|
|
|
|
|
+ 'function.panel.active.connector.type',
|
|
|
|
|
+ activeConnectorUdfs.connector.type
|
|
|
|
|
+ );
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const setCategoriesFromConnector = connector => {
|
|
|
|
|
+ this.availableConnectorUdfs().some(availableConnectorUdfs => {
|
|
|
|
|
+ if (availableConnectorUdfs.connector.type === connector.type) {
|
|
|
|
|
+ this.activeConnectorUdfs(availableConnectorUdfs);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ this.activeConnector.subscribe(connector => {
|
|
|
|
|
+ if (connector) {
|
|
|
|
|
+ setCategoriesFromConnector(connector);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const configUpdated = () => {
|
|
|
|
|
+ const lastActiveConnectorType =
|
|
|
|
|
+ (this.activeConnector() && this.activeConnector().type) ||
|
|
|
|
|
+ apiHelper.getFromTotalStorage('assist', 'function.panel.active.connector.type');
|
|
|
|
|
+
|
|
|
|
|
+ const availableConnectorUdfs = filterEditorConnectors(hasUdfCategories).map(
|
|
|
|
|
+ connector => new ConnectorUdfCategories(connector)
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ availableConnectorUdfs.sort((a, b) =>
|
|
|
|
|
+ a.connector.displayName.localeCompare(b.connector.displayName)
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ this.availableConnectorUdfs(availableConnectorUdfs);
|
|
|
|
|
+
|
|
|
|
|
+ let activeConnectorUdfs;
|
|
|
|
|
+
|
|
|
|
|
+ if (lastActiveConnectorType) {
|
|
|
|
|
+ activeConnectorUdfs = this.availableConnectorUdfs().find(
|
|
|
|
|
+ connectorUdf => connectorUdf.connector.type === lastActiveConnectorType
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!activeConnectorUdfs && this.availableConnectorUdfs().length) {
|
|
|
|
|
+ activeConnectorUdfs = this.availableConnectorUdfs()[0];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ this.activeConnectorUdfs(activeConnectorUdfs);
|
|
|
|
|
+
|
|
|
|
|
+ setCategoriesFromConnector(this.activeConnector());
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ configUpdated();
|
|
|
|
|
+ huePubSub.subscribe(CONFIG_REFRESHED_EVENT, configUpdated);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
componentUtils.registerStaticComponent(NAME, AssistFunctionsPanel, TEMPLATE);
|
|
componentUtils.registerStaticComponent(NAME, AssistFunctionsPanel, TEMPLATE);
|
|
|
|
|
|
|
|
export default AssistFunctionsPanel;
|
|
export default AssistFunctionsPanel;
|