UNPKG

gethue

Version:

Hue is an Open source SQL Query Editor for Databases/Warehouses

1,011 lines (931 loc) 41.7 kB
// 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 * as ko from 'knockout'; import { CancellablePromise } from '../../../api/cancellablePromise'; import AsteriskContextTabs from './asteriskContextTabs'; import CollectionContextTabs from './collectionContextTabs'; import DataCatalogContext from './dataCatalogContext'; import DocumentContext, { DOCUMENT_CONTEXT_TEMPLATE } from './documentContext'; import FunctionContextTabs, { FUNCTION_CONTEXT_TEMPLATE } from './functionContext'; import { DOCUMENT_CONTEXT_FOOTER } from './ko.documentContextFooter'; import LangRefContext from './langRefContext'; import PartitionContext, { PARTITION_CONTEXT_TEMPLATE } from './partitionContext'; import ResizeHelper from './resizeHelper'; import StorageContext from './storageContext'; import { getNamespaces } from '../../../catalog/contextCatalog'; import dataCatalog from '../../../catalog/dataCatalog'; import { GET_KNOWN_CONFIG_TOPIC } from '../../../config/events'; import { findEditorConnector } from '../../../config/hueConfig'; import { ASSIST_KEY_COMPONENT } from '../../../ko/components/assist/ko.assistKey'; import componentUtils from '../../../ko/components/componentUtils'; import huePubSub from '../../../utils/huePubSub'; import I18n from '../../../utils/i18n'; import { getFromLocalStorage } from '../../../utils/storageUtils'; export const CONTEXT_POPOVER_CLASS = 'hue-popover'; export const HIDE_CONTEXT_POPOVER_EVENT = 'context.popover.hide'; export const CONTEXT_POPOVER_HIDDEN_EVENT = 'context.popover.hidden'; export const SHOW_CONTEXT_POPOVER_EVENT = 'context.popover.show'; export const NAME = 'context-popover'; // prettier-ignore const SUPPORT_TEMPLATES = ` <script type="text/html" id="context-popover-footer"> <div class="context-popover-flex-bottom-links"> <div class="context-popover-link-row"> <a href="javascript: void(0);" class="inactive-action" data-bind="visible: showInAssistEnabled, publish: 'context.popover.show.in.assist'"> <i style="font-size: 11px;" title="${I18n( 'Show in Assist...' )}" class="fa fa-search"></i> ${I18n('Assist')} </a> <!-- ko if: isDocument --> <!-- ko with: contents --> <!-- ko if: documentId --> <a href="javascript: void(0);" class="inactive-action" data-bind="click: download"> <i style="font-size: 11px;" title="${I18n('Download')}" class="fa fa-download"></i> ${I18n('Download')} </a> <!-- /ko --> <a href="javascript: void(0);" class="inactive-action" data-bind="click: open"> <i style="font-size: 11px;" title="${I18n('Open')}" class="fa fa-file-o"></i> ${I18n('Open')} </a> <!-- /ko --> <!-- /ko --> <!-- ko if: expandColumnsEnabled --> <!-- ko with: contents.data --> <!-- ko if: selectedColumns().length > 0 --> <a class="inactive-action pointer" data-bind="click: expand">${I18n( 'Expand to selected columns' )}</a> <!-- /ko --> <!-- ko if: selectedColumns().length === 0 --> <a class="inactive-action pointer" data-bind="click: expand">${I18n( 'Expand to all columns' )}</a> <!-- /ko --> <!-- /ko --> <!-- /ko --> </div> <!-- ko if: isDocument --> <div class="context-popover-bottom-attributes"> <!-- ko with: contents --> <!-- ko component: { name: '${ DOCUMENT_CONTEXT_FOOTER }', params: { popoverData: $data } } --><!-- /ko --> <!-- /ko --> </div> <!-- /ko --> </div> </script> <script type="text/html" id="context-popover-collection-stats-details"> <div class="context-popover-flex-fill"> <div style="padding: 8px"> <div data-bind="with: stats"> <!-- ko hueSpinner: { spin: $parent.loadingStats, center: true, size: 'large' } --><!-- /ko --> <div class="alert" data-bind="visible: !$parent.loadingStats() && !$parent.statsSupported()">${I18n( 'This field does not support stats' )}</div> <div class="alert" data-bind="visible: !$parent.loadingStats() && $parent.statsSupported() && $data.data().length == 0">${I18n( 'There are no stats to be shown' )}</div> <table style="width: 100%" data-bind="visible: !$parent.loadingStats() && $data.data().length > 0" class="table table-condensed"> <tbody data-bind="foreach: $data.data"> <tr> <td style="vertical-align: top"><strong data-bind="text: key"></strong></td> <!-- ko if: key == 'facets' --> <td> <!-- ko if: val[Object.keys(val)[0]] != null --> <table> <tbody data-bind="foreach: Object.keys(val[Object.keys(val)[0]])"> <tr> <td style="vertical-align: top; padding-left: 4px; padding-right: 4px"><strong data-bind="text: $data"></strong></td> <td data-bind="template: 'context-popover-collection-stats-facets'"></td> </tr> </tbody> </table> <!-- /ko --> <!-- ko ifnot: val[Object.keys(val)[0]] != null --> ${I18n('Not available')} <!-- /ko --> </td> <!-- /ko --> <!-- ko ifnot: key == 'facets' --> <td data-bind="text: val"></td> <!-- /ko --> </tr> </tbody> </table> </div> </div> </div> </script> <script type="text/html" id="context-popover-collection-stats-facets"> <table style="width: 100%"> <tbody data-bind="foreach: Object.keys($parent.val[Object.keys($parent.val)[0]][$data])"> <tr> <td style="vertical-align: top; padding-left: 4px; padding-right: 4px"><strong data-bind="text: $data"></strong></td> <!-- ko ifnot: $data == 'facets' --> <td style="vertical-align: top" data-bind="text: $parents[1].val[Object.keys($parents[1].val)[0]][$parent][$data]"></td> <!-- /ko --> </tr> </tbody> </table> </script> <script type="text/html" id="context-popover-collection-terms-details"> <div class="context-popover-flex-fill"> <input type="text" data-bind="value: terms.prefix, clearable: terms.prefix, valueUpdate:'afterkeydown'" placeholder="${I18n( 'Filter...' )}" class="pull-right"> <div data-bind="with: terms"> <!-- ko hueSpinner: { spin: $parent.loadingTerms, center: true, size: 'large' } --><!-- /ko --> <div class="clearfix"></div> <div class="alert" data-bind="visible: !$parent.loadingTerms() && $data.data().length == 0">${I18n( 'There are no terms to be shown' )}</div> <table style="width: 100%" data-bind="visible: !$parent.loadingTerms() && $data.data().length > 0" class="table table-condensed"> <tbody data-bind="foreach: $data.data"> <tr> <td data-bind="text: val.value"></td> <td style="width: 40px"> <div class="progress"> <div class="bar-label" data-bind="text:val.count"></div> <div class="bar bar-info" style="margin-top:-20px;" data-bind="style: {'width': ((val.count / $parent.data()[0].val.count) * 100) + '%'}"></div> </div> </td> </tr> </tbody> </table> </div> </div> </script> <script type="text/html" id="context-popover-asterisk-details"> <div class="context-popover-flex-fill"> <!-- ko component: { name: 'sql-columns-table', params: { columns: columns, scrollToColumns: false } } --><!-- /ko --> </div> </script> <script type="text/html" id="context-hue-app-details"> <div class="context-popover-flex-fill" style="overflow: auto;" data-bind="with: data"> <div style="padding: 8px"> <div style="width:100%; text-align: center; margin-top: 30px; font-size: 100px; color: #787878;" data-bind="template: { name: 'app-icon-template', data: { icon: interpreter.type } }"></div> <div style="width: 100%; margin-top: 20px; text-align:center"> <a style="font-size: 20px;" href="javascript:void(0)" data-bind="text: interpreter.displayName, hueLink: interpreter.page, click: function () { $parents[1].close(); }, attr: { 'title': interpreter.tooltip }"></a> </div> </div> </div> </script> <script type="text/html" id="context-popover-contents"> <div class="context-popover-content"> <!-- ko with: contents --> <!-- ko if: typeof tabs !== 'undefined' --> <ul class="nav nav-tabs context-popover-tabs" data-bind="foreach: tabs"> <li data-bind="click: function () { $parent.activeTab(id); }, css: { 'active' : $parent.activeTab() === id }"> <a class="context-popover-tab" data-toggle="tab" data-bind="text: label, attr: { href: '#' + id }"></a> </li> </ul> <div class="context-popover-tab-container" data-bind="foreach: tabs"> <div class="context-popover-tab-pane tab-pane" data-bind="visible : $parent.activeTab() === id, attr: { id: id }, css: { 'active' : $parent.activeTab() === id }"> <div class="context-popover-flex"> <!-- ko with: templateData --> <div class="context-popover-flex-fill" data-bind="visible: loading"><!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko --></div> <!-- ko if: ! loading() && hasErrors() --> <div class="context-popover-flex-fill"> <div class="alert"> <span data-bind="text: $parent.errorText"></span> <!-- ko if: $parent.enableSampleError && $parents[1].activeTab() === 'sample' --> <a href="javascript:void(0);" data-bind="click: function(){ huePubSub.publish('sample.error.insert.click', $data); huePubSub.publish('context.popover.hide');}">${I18n( 'Insert ' )}<span data-bind="text:$parent.title"></span> ${I18n('sample query')}</a> ${I18n( 'at cursor' )} <!-- /ko --> </div> </div> <!-- /ko --> <!-- ko if: ! loading() && ! hasErrors() --> <!-- ko template: { name: $parent.template } --><!-- /ko --> <!-- /ko --> <!-- /ko --> <!-- ko template: { name: 'context-popover-footer', data: $parents[1] } --><!-- /ko --> </div> </div> </div> <!-- /ko --> <!-- ko if: typeof tabs === 'undefined' --> <div class="context-popover-flex-fill" data-bind="visible: loading"><!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko --></div> <!-- ko if: !loading() && hasErrors() --> <div class="context-popover-flex-fill"> <div class="alert" data-bind="text: errorText"></div> </div> <!-- /ko --> <!-- ko if: !loading() && !hasErrors() --> <!-- ko template: { name: template } --><!-- /ko --> <!-- /ko --> <!-- ko template: { name: 'context-popover-footer', data: $parent } --><!-- /ko --> <!-- /ko --> <!-- /ko --> </div> </script> <script type="text/html" id="context-catalog-doc-title"> </script> <script type="text/html" id="context-lang-ref-title"> <div class="hue-popover-title" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-right: 20px;"> <i class="fa fa-book muted" style="margin-top: 3px"></i> <span class="blue" style="padding-left: 4px;" data-bind="text: title"></span> <div class="hue-popover-title-actions"> <a class="pointer inactive-action" title="${I18n( 'Pin' )}" data-bind="visible: popover.pinEnabled, click: popover.pin"><i class="fa fa-fw fa-thumb-tack"></i></a> <a class="pointer inactive-action" title="${I18n( 'Close' )}" data-bind="visible: !popover.closeDisabled, click: popover.close"><i class="fa fa-fw fa-times"></i></a> </div> </div> </script> <script type="text/html" id="context-lang-ref-contents"> <div class="context-popover-content"> <div class="context-popover-flex-fill context-popover-docs-details" data-bind="htmlUnsecure: body"></div> <div class="context-popover-flex-bottom-links"> <div class="context-popover-link-row"> <a class="inactive-action pointer" data-bind="click: openInRightAssist"> <i style="font-size: 11px;" title="${I18n( 'Show in Assist...' )}" class="fa fa-search"></i> ${I18n('Assist')} </a> </div> </div> </div> </script> <script type="text/html" id="context-catalog-entry-title"> <div class="hue-popover-title"> <!-- ko if: catalogEntry().isTable() --> <span class="hue-popover-title-secondary-icon-container"> <!-- ko hueSpinner: { spin: loading, inline: true } --><!-- /ko --> <!-- ko ifnot: loading --> <!-- ko if:catalogEntry().isIcebergTable() --> <i class="hue-popover-title-icon fa muted fa-snowflake-o" title="${I18n( 'Iceberg table' )}"></i> <!-- /ko --> <!-- ko ifnot:catalogEntry().isIcebergTable() --> <i class="hue-popover-title-icon fa muted fa-table"></i> <!-- /ko --> <!-- /ko --> </span> <!-- /ko --> <!-- ko ifnot: catalogEntry().isTable() --> <i class="hue-popover-title-icon fa muted" data-bind="css: catalogEntry() && (catalogEntry().isView() || parentIsView()) ? 'fa-eye' : (catalogEntry().isDatabase() ? 'fa-database' : (catalogEntry().isModel() ? 'fa-puzzle-piece' : 'fa-table'))"></i> <!-- /ko --> <span class="hue-popover-title-text" data-bind="foreach: breadCrumbs"> <!-- ko ifnot: isActive --><div><a href="javascript: void(0);" data-bind="click: makeActive, text: name"></a>.</div><!-- /ko --> <!-- ko if: isActive --> <div> <span data-bind="text: name"></span> <!-- ko with: catalogEntry --> <!-- ko if: isField() --> (<span data-bind="text: getType()"></span>) <!-- ko if: isKey() --> <!-- ko component: { name: '${ ASSIST_KEY_COMPONENT }', params: { entry: $data, onForeignKeyClick: $parents[1].setEntry.bind($parents[1]) } } --><!-- /ko --> <!-- /ko --> <!-- /ko --> <!-- /ko --> </div> <!-- /ko --> </span> <div class="hue-popover-title-actions"> <!-- ko hueSpinner: { spin: loading, inline: true } --><!-- /ko --> <a class="pointer inactive-action" title="${I18n( 'Refresh' )}" data-bind="visible: !loading() && catalogEntry() && !catalogEntry().isTemporary, click: refresh"><i class="fa fa-fw fa-refresh"></i></a> <a class="pointer inactive-action" title="${I18n( 'Pin' )}" data-bind="visible: popover.pinEnabled && catalogEntry() && !catalogEntry().isTemporary, click: popover.pin"><i class="fa fa-fw fa-thumb-tack"></i></a> <a class="pointer inactive-action" title="${I18n( 'Close' )}" data-bind="visible: !popover.closeDisabled, click: popover.close"><i class="fa fa-fw fa-times"></i></a> </div> </div> </script> <script type="text/html" id="context-catalog-entry-contents"> <div class="context-popover-content"> <div class="context-popover-flex-fill" data-bind="visible: loading"><!-- ko hueSpinner: { spin: loading, center: true, size: 'xlarge' } --><!-- /ko --></div> <!-- ko if: !loading() && hasErrors() --> <div class="context-popover-flex-fill"> <div class="alert" data-bind="text: errorText"></div> </div> <!-- /ko --> <!-- ko if: !loading() && !hasErrors() && typeof catalogEntry() !== 'undefined'--> <div class="context-popover-flex-fill" data-bind="with: catalogEntry"> <div class="context-popover-inner-content"> <!-- ko if: $parent.comment() && !isTemporary --> <div class="context-popover-comment" data-bind="attr: { 'title': $parent.comment }, multiLineEllipsis: { expanded: $parent.commentExpanded, expandable: true, expandClass: 'context-popover-comment-expanded' }, text: $parent.comment"></div> <!-- /ko --> <!-- ko ifnot: $parent.commentExpanded --> <!-- ko if: window.HAS_CATALOG && !isTemporary && (getDialect() === 'hive' || getDialect() === 'impala') --> <div data-bind="component: { name: 'nav-tags', params: { catalogEntry: $data, overflowEllipsis: true } }"></div> <!-- /ko --> <!-- ko if: isView() && $parent.viewSql() --> <a href="javascript:void(0);" style="text-align: right; margin-bottom: 5px;" data-bind="toggle: $parent.viewSqlVisible, text: $parent.viewSqlVisible() ? '${I18n( 'Show columns' )}' : '${I18n('Show view SQL')}'"></a> <!-- /ko --> <!-- ko if: $parent.viewSqlVisible --> <div class="context-popover-sql" data-bind="highlight: { value: $parent.viewSql, enableOverflow: true, formatted: true, dialect: getDialect() }"></div> <!-- /ko --> <!-- ko ifnot: $parent.viewSqlVisible --> <!-- ko component: { name: 'catalog-entries-list', params: { catalogEntry: $data, onClick: $parent.catalogEntry, onSampleClick: $parent.onSampleClick } } --><!-- /ko --> <!-- /ko --> <!-- /ko --> </div> </div> <div class="context-popover-flex-bottom-links"> <div class="context-popover-link-row"> <!-- ko if: catalogEntry --> <a class="inactive-action pointer" data-bind="visible: popover.showInAssistEnabled || catalogEntry() !== originalCatalogEntry, click: showInAssist"> <i style="font-size: 11px;" title="${I18n( 'Show in Assist...' )}" class="fa fa-search"></i> ${I18n('Assist')} </a> <!-- ko if: window.HAS_SQL_DASHBOARD --> <a class="inactive-action pointer" data-bind="visible: openActionsEnabled, click: openInDashboard"> <i style="font-size: 11px;" title="${I18n( 'Open in Dashboard...' )}" class="fa fa-external-link"></i> ${I18n('Dashboard')} </a> <!-- /ko --> <!-- ko if: catalogEntry().getDialect() !== 'solr' && openActionsEnabled() --> <a class="inactive-action pointer" data-bind="click: openInTableBrowser"> <i style="font-size: 11px;" title="${I18n( 'Open in Table Browser...' )}" class="fa fa-external-link"></i> ${I18n('Table Browser')} </a> <!-- /ko --> <!-- /ko --> </div> </div> <!-- /ko --> </div> </script> <script type="text/html" id="context-storage-entry-title"> <div class="hue-popover-title"> <i class="hue-popover-title-icon fa muted" data-bind="css: storageEntry() && storageEntry().definition.type === 'dir' ? 'fa-folder-o' : 'fa-file-o'"></i> <span class="hue-popover-title-text" data-bind="foreach: breadCrumbs"> <!-- ko ifnot: isActive --><div><a href="javascript: void(0);" data-bind="click: makeActive, text: name"></a><!-- ko if: $index() > 0 -->/<!-- /ko --></div><!-- /ko --> <!-- ko if: isActive --> <div> <span data-bind="text: name"></span> </div> <!-- /ko --> </span> <div class="hue-popover-title-actions"> <!-- ko hueSpinner: { spin: loading, inline: true } --><!-- /ko --> <a class="pointer inactive-action" title="${I18n( 'Go Home' )}" data-bind="visible: !loading(), click: goHome"><i class="fa fa-fw fa-home"></i></a> <a class="pointer inactive-action" title="${I18n( 'Close' )}" data-bind="visible: !popover.closeDisabled, click: popover.close"><i class="fa fa-fw fa-times"></i></a> </div> </div> </script> <script type="text/html" id="context-storage-entry-contents"> <div class="context-popover-content" data-bind="with: storageEntry"> <!-- ko if: !loading() && hasErrors() --> <div class="context-popover-flex-fill"> <div class="alert" data-bind="text: errorText"></div> </div> <!-- /ko --> <div class="context-popover-flex-fill" data-bind="visible: loading"><!-- ko hueSpinner: { spin: loading, center: true, size: 'xlarge' } --><!-- /ko --></div> <!-- ko if: !loading() && !hasErrors() --> <!-- ko with: definition --> <div class="context-popover-flex-attributes"> <!-- ko if: typeof humansize !== 'undefined' --> <div class="context-popover-attribute"><div>${I18n( 'Size' )}</div><div data-bind="text: humansize"></div></div> <!-- /ko --> <!-- ko if: typeof stats !== 'undefined' --> <!-- ko with: stats --> <!-- ko if: user --> <div class="context-popover-attribute"><div>${I18n( 'Owner' )}</div><div data-bind="text: user"></div></div> <!-- /ko --> <!-- /ko --> <!-- /ko --> <!-- ko if: typeof rwx !== 'undefined' --> <div class="context-popover-attribute"><div>${I18n( 'Permissions' )}</div><div data-bind="text: rwx"></div></div> <!-- /ko --> </div> <!-- /ko --> <!-- ko if: definition.type === 'dir' --> <div class="context-popover-flex-fill storage-entry-container" data-bind="fetchMore: { fetchMore: fetchMore.bind($data), hasMore: hasMorePages, loadingMore: loadingMore.bind($data) }"> <table class="table table-condensed table-nowrap"> <thead> <tr> <th width="1%"></th> <th>${I18n('Name')}</th> <th>${I18n('Size')}</th> <th>${I18n('Permissions')}</th> </tr> </thead> <tbody> <!-- ko if: $parent.storageEntry().parent --> <tr> <td><i class="fa fa-folder-o"></i></td> <td><a href="javascript: void(0);" data-bind="click: function () { $parent.storageEntry($parent.storageEntry().parent) }">..</a></td> <td data-bind="text: $parent.storageEntry().definition.humansize"></td> <td data-bind="text: $parent.storageEntry().definition.rwx"></td> </tr> <!-- /ko --> <!-- ko foreach: entries --> <tr> <td><i class="fa" data-bind="css: definition.type === 'dir' ? 'fa-folder-o' : 'fa-file-o'"></i></td> <td><a href="javascript: void(0);" data-bind="click: function () { $parents[1].storageEntry($data) }, text: definition.name"></a></td> <td data-bind="text: definition.humansize"></td> <td data-bind="text: definition.rwx"></td> </tr> <!-- /ko --> </tbody> </table> </div> <!-- /ko --> <!-- ko if: definition.type !== 'dir' --> <div class="context-popover-flex-header"><div class="context-popover-header">${I18n( 'Preview' )}</div></div> <div class="context-popover-flex-fill storage-entry-container"> <div data-bind="with: preview"> <!-- ko if: view && view.contents --> <pre data-bind="text: view.contents"></pre> <!-- /ko --> <!-- ko if: view && !view.contents --> <div class="empty-file-contents">${I18n('Empty file...')}</div> <!-- /ko --> </div> </div> <!-- /ko --> <!-- /ko --> <div class="context-popover-flex-bottom-links"> <div class="context-popover-link-row"> <!-- ko ifnot: loading --> <a class="inactive-action pointer" data-bind="click: $parent.openInFileBrowser"> <i style="font-size: 11px;" title="${I18n( 'Open in File Browser...' )}" class="fa fa-external-link"></i> ${I18n('File Browser')} </a> <!-- ko if: typeof $parent.editorLocation !== 'undefined' --> <a class="inactive-action pointer" data-bind="click: function () { $parent.replaceInEditor($data, $parent) }"> <i style="font-size: 11px;" title="${I18n( 'Replace the editor content...' )}" class="fa fa-pencil"></i> ${I18n('Insert in the editor')} </a> <!-- /ko --> <!-- /ko --> </div> </div> </div> </script> `; // prettier-ignore const CONTEXT_POPOVER_TEMPLATE = ` <div class="${ CONTEXT_POPOVER_CLASS }" data-bind="css: orientationClass, style: { 'left': left() + 'px', 'top': top() + 'px', 'width': width() + 'px', height: height() + 'px' }, resizable: { containment: 'document', handles: resizeHelper.resizableHandles, start: resizeHelper.resizeStart, stop: resizeHelper.resizeStop, resize: resizeHelper.resize }"> <div class="hue-popover-arrow" data-bind="style: { 'margin-left': leftAdjust() + 'px', 'margin-top': topAdjust() + 'px' }"></div> <!-- ko if: typeof titleTemplate !== 'undefined' --> <!-- ko template: { name: titleTemplate, data: contents } --><!-- /ko --> <!-- /ko --> <!-- ko if: typeof titleTemplate === 'undefined' --> <div class="hue-popover-title" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-right: 30px;"> <i class="fa muted" data-bind="css: iconClass" style="margin-top: 3px"></i> <span style="padding-left: 4px;" data-bind="text: title"></span> <!-- ko if: typeof subtitle !== 'undefined' && subtitle --> <span class="muted" style="padding-left: 4px;" data-bind="html: subtitle"></span> <!-- /ko --> <div style="position: absolute; right: 6px; top: 8px;"> <a class="pointer inactive-action" data-bind="visible: pinEnabled, click: pin"><i class="fa fa-fw fa-thumb-tack"></i></a> <a class="pointer inactive-action" data-bind="click: close"><i class="fa fa-fw fa-times"></i></a> </div> </div> <!-- /ko --> <!-- ko if: typeof contentsComponent !== 'undefined' --> <!-- ko component: { name: contentsComponent, params: data } --><!-- /ko --> <!-- /ko --> <!-- ko if: typeof contentsTemplate !== 'undefined' --> <!-- ko template: { name: contentsTemplate, data: contents } --><!-- /ko --> <!-- /ko --> <!-- ko if: typeof contentsTemplate === 'undefined' && typeof contentsComponent === 'undefined' --> <!-- ko template: 'context-popover-contents' --><!-- /ko --> <!-- /ko --> </div> `; // prettier-ignore const GLOBAL_SEARCH_TEMPLATE = ` <!-- ko if: isCatalogEntry --> <!-- ko with: contents --> <div class="global-search-catalog-entry"> <!-- ko template: 'context-catalog-entry-title' --><!-- /ko --> <!-- ko template: 'context-catalog-entry-contents' --><!-- /ko --> </div> <!-- /ko --> <!-- /ko --> <!-- ko ifnot: isCatalogEntry --> <!-- ko template: 'context-popover-contents' --><!-- /ko --> <!-- /ko --> `; const HALF_ARROW = 6; let preventHide = false; const hidePopover = function () { if (!preventHide) { const $contextPopover = $('#contextPopover'); if ($contextPopover.length > 0) { ko.cleanNode($contextPopover[0]); $contextPopover.remove(); $(document).off('click.context'); huePubSub.publish(CONTEXT_POPOVER_HIDDEN_EVENT); } } }; class ContextPopoverViewModel { constructor(params) { const self = this; self.disposals = []; self.left = ko.observable(0); self.top = ko.observable(0); const popoverSize = getFromLocalStorage('assist.popover.size', { width: 450, height: 400 }); self.width = ko.observable(popoverSize.width); self.height = ko.observable(popoverSize.height); self.leftAdjust = ko.observable(0); self.topAdjust = ko.observable(0); self.data = params.data; self.connector = params.connector; self.sourceType = params.sourceType; self.namespace = params.namespace; self.compute = params.compute; self.defaultDatabase = params.defaultDatabase; self.close = hidePopover; self.onSampleClick = params.onSampleClick; const orientation = params.orientation || 'bottom'; self.contents = null; self.resizeHelper = new ResizeHelper(orientation, self.leftAdjust, self.topAdjust, noHide => { preventHide = noHide; }); if (typeof params.source.element !== 'undefined') { // Track the source element and close the popover if moved const $source = $(params.source.element); const originalSourceOffset = $source.offset(); let currentSourceOffset; const detectMoveInterval = window.setInterval(() => { currentSourceOffset = $source.offset(); if (currentSourceOffset.top !== originalSourceOffset.top) { hidePopover(); } }, 200); self.disposals.push(() => { window.clearInterval(detectMoveInterval); }); } const windowWidth = $(window).width(); const fitHorizontally = function () { let left = params.source.left + Math.round((params.source.right - params.source.left) / 2) - self.width() / 2; if (left + self.width() > windowWidth - 10) { self.leftAdjust(left + self.width() - windowWidth + 5); left = windowWidth - self.width() - 10; } else if (left < 10) { self.leftAdjust(left - 10 - HALF_ARROW); left = 10; } else { self.leftAdjust(-HALF_ARROW); } self.left(left); }; const windowHeight = $(window).height(); const fitVertically = function () { let top = params.source.top + Math.round((params.source.bottom - params.source.top) / 2) - self.height() / 2; if (top + self.height() > windowHeight - 10) { self.topAdjust(top + self.height() - windowHeight + 5); top = windowHeight - self.height() - 10; } else if (top < 10) { self.topAdjust(top - 10 - HALF_ARROW); top = 10; } else { self.topAdjust(-HALF_ARROW); } self.top(top); }; switch (orientation) { case 'top': fitHorizontally(); self.top(params.source.top - self.height()); break; case 'right': fitVertically(); self.left(params.source.right); break; case 'bottom': fitHorizontally(); self.top(params.source.bottom); break; case 'left': fitVertically(); self.left(params.source.left - self.width()); } self.isSolr = params.sourceType === 'solr'; self.isComplex = params.data.type === 'complex'; self.isFunction = params.data.type === 'function'; self.isStorageEntry = params.data.type === 'storageEntry'; self.isAsterisk = params.data.type === 'asterisk'; self.isDocument = params.data.type.toLowerCase() === 'hue'; self.isCollection = params.data.type === 'collection'; self.isCatalogEntry = !!params.data.catalogEntry; self.isLangRef = params.data.type === 'statementType'; if ( self.isCatalogEntry && params.data.catalogEntry.getDialect() === 'solr' && params.data.catalogEntry.isField() ) { self.isCollection = true; self.isCatalogEntry = false; self.namespace = params.data.catalogEntry.namespace; self.compute = params.data.catalogEntry.compute; } self.showInAssistEnabled = (typeof params.showInAssistEnabled !== 'undefined' ? params.showInAssistEnabled : true) && (self.isDocument || self.isCollection || self.isCatalogEntry); self.openInDashboardEnabled = self.isCatalogEntry && params.data.catalogEntry.path.length <= 2; self.openInTableBrowserEnabled = self.isCatalogEntry && params.data.catalogEntry.path.length <= 2; self.expandColumnsEnabled = self.isAsterisk; self.pinEnabled = params.pinEnabled && !self.isFunction && !self.isAsterisk && !self.isStorageEntry && !self.isCatalogEntry; if (params.data.type === 'quickQuery') { self.contentsComponent = 'quick-query-context'; self.title = I18n('Quick Query'); self.iconClass = 'fa-play'; self.pinEnabled = false; } else if (self.isCatalogEntry) { self.contents = new DataCatalogContext({ popover: self, catalogEntry: params.data.catalogEntry }); self.titleTemplate = 'context-catalog-entry-title'; self.contentsTemplate = 'context-catalog-entry-contents'; } else if (self.isFunction) { self.contents = new FunctionContextTabs(self.data, self.connector); self.title = self.data.function; self.iconClass = 'fa-superscript'; } else if (self.isStorageEntry) { self.contents = new StorageContext({ popover: self, storageEntry: params.data.storageEntry, editorLocation: params.data.editorLocation }); self.titleTemplate = 'context-storage-entry-title'; self.contentsTemplate = 'context-storage-entry-contents'; } else if (self.isAsterisk) { self.contents = new AsteriskContextTabs( self.data, self.connector, self.namespace, self.compute, self.defaultDatabase ); self.title = '*'; self.iconClass = 'fa-table'; } else if (self.isDocument) { self.contents = new DocumentContext(self.data.definition); self.titleTemplate = 'context-catalog-doc-title'; } else if (self.isCollection) { self.contents = new CollectionContextTabs(self.data); self.title = self.data.catalogEntry.path.slice(1).join('.'); self.iconClass = 'fa-search'; } else if (self.isLangRef) { self.contents = new LangRefContext({ popover: self, data: params.data }); self.titleTemplate = 'context-lang-ref-title'; self.contentsTemplate = 'context-lang-ref-contents'; } else { self.title = ''; self.iconClass = 'fa-info'; } self.orientationClass = 'hue-popover-' + orientation; if (params.delayedHide) { let hideTimeout = -1; const onLeave = function () { hideTimeout = window.setTimeout(() => { $('.hue-popover').fadeOut(200, () => { hidePopover(); }); }, 1000); }; const onEnter = function () { window.clearTimeout(hideTimeout); }; $(params.delayedHide) .add($('.hue-popover')) .on('mouseleave', onLeave) .on('mouseenter', onEnter); const keepPopoverOpenOnClick = function () { window.clearTimeout(hideTimeout); $(params.delayedHide) .add($('.hue-popover')) .off('mouseleave', onLeave) .off('mouseenter', onEnter); }; $('.hue-popover').on('click', keepPopoverOpenOnClick); self.disposals.push(() => { $(params.delayedHide) .add($('.hue-popover')) .off('mouseleave', onLeave) .off('mouseenter', onEnter); $('.hue-popover').off('click', keepPopoverOpenOnClick); }); } const closeOnEsc = function (e) { if (e.keyCode === 27) { hidePopover(); } }; $(document).on('keyup', closeOnEsc); self.disposals.push(() => { $(document).off('keyup', closeOnEsc); }); window.setTimeout(() => { $(document).off('click.context'); $(document).on('click.context', event => { if ( $.contains(document, event.target) && !$.contains($('#contextPopover')[0], event.target) && ($('.modal')[0].length === 0 || !$.contains($('.modal')[0], event.target)) ) { hidePopover(); } }); }, 0); self.disposals.push(() => { $(document).off('click.context'); }); } dispose() { const self = this; while (self.disposals.length) { self.disposals.pop()(); } if (self.contents && self.contents.dispose) { self.contents.dispose(); } huePubSub.publish('context.popover.dispose'); } pin() { const self = this; hidePopover(); if (self.contents && typeof self.contents.sample !== 'undefined') { self.contents.sample.fetchedData(undefined); } huePubSub.publish('sql.context.pin', self); if (self.contents && self.contents.activeTab() === 'sample') { self.contents.refetchSamples(); } } } componentUtils .registerComponent( NAME, ContextPopoverViewModel, SUPPORT_TEMPLATES + DOCUMENT_CONTEXT_TEMPLATE + FUNCTION_CONTEXT_TEMPLATE + PARTITION_CONTEXT_TEMPLATE + CONTEXT_POPOVER_TEMPLATE ) .then(() => { huePubSub.subscribe(HIDE_CONTEXT_POPOVER_EVENT, hidePopover); huePubSub.subscribe(SHOW_CONTEXT_POPOVER_EVENT, details => { hidePopover(); const $contextPopover = $( '<div id="contextPopover" data-bind="component: { name: \'context-popover\', params: $data }" ></div>' ); $('body').append($contextPopover); ko.applyBindings(details, $contextPopover[0]); huePubSub.publish('context.popover.shown'); }); }); class SqlContextContentsGlobalSearch { constructor(params) { const self = this; self.contents = ko.observable(); self.disposals = []; self.isCatalogEntry = params.data.type === 'catalogEntry' || params.data.type.toLowerCase() === 'database' || params.data.type.toLowerCase() === 'table' || params.data.type.toLowerCase() === 'field' || params.data.type.toLowerCase() === 'view'; self.isDocument = params.data.type.toLowerCase() === 'hue'; self.isPartition = params.data.type.toLowerCase() === 'partition'; self.close = params.globalSearch.close.bind(params.globalSearch); // These are currently not in the global search results self.isStorageEntry = false; self.isAsterisk = false; self.isComplex = false; self.isFunction = false; self.showInAssistEnabled = !self.isHueApp; self.openInDashboardEnabled = false; self.openInTableBrowserEnabled = false; self.expandColumnsEnabled = self.isAsterisk; self.closeDisabled = true; // Global search has it's own close const adaptedData = { identifierChain: [] }; const path = params.data.originalName.split('.'); path.forEach(part => { adaptedData.identifierChain.push({ name: part }); }); let connectorId = params.data.sourceType && params.data.sourceType.toLowerCase(); if (!connectorId || connectorId === 'hive') { huePubSub.publish(GET_KNOWN_CONFIG_TOPIC, clusterConfig => { if (clusterConfig) { const defaultEditor = clusterConfig['default_sql_interpreter']; if (!connectorId || (connectorId === 'hive' && defaultEditor === 'impala')) { connectorId = defaultEditor; } } }); } if (self.isCatalogEntry) { // TODO: Connector, Namespace and compute selection for global search results? let connector = findEditorConnector(connector => connector.id === connectorId); if (!connector) { // TODO: Global search results are referring to dialect and not type connector = findEditorConnector(connector => connector.dialect === connectorId); } getNamespaces({ connector }) .then(context => { dataCatalog .getEntry({ namespace: context.namespaces[0], compute: context.namespaces[0].computes[0], connector: connector, path: path, definition: { type: params.data.type.toLowerCase() } }) .then(catalogEntry => { catalogEntry.navigatorMeta = params.data; catalogEntry.navigatorMetaPromise = CancellablePromise.resolve( catalogEntry.navigatorMeta ); catalogEntry.saveLater(); self.contents(new DataCatalogContext({ popover: self, catalogEntry: catalogEntry })); }); }) .catch(); } else if (self.isDocument) { self.contents(new DocumentContext(params.data)); } else if (self.isPartition) { self.contents(new PartitionContext(params.data)); } } dispose() { const self = this; while (self.disposals.length) { self.disposals.pop()(); } if (self.contents && self.contents.dispose) { self.contents.dispose(); } huePubSub.publish('context.popover.dispose'); } } componentUtils.registerComponent( 'context-popover-contents-global-search', SqlContextContentsGlobalSearch, SUPPORT_TEMPLATES + DOCUMENT_CONTEXT_TEMPLATE + FUNCTION_CONTEXT_TEMPLATE + PARTITION_CONTEXT_TEMPLATE + GLOBAL_SEARCH_TEMPLATE );