UNPKG

gethue

Version:

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

891 lines (836 loc) 45.6 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 { ASSIST_DB_HIGHLIGHT_EVENT, ASSIST_DB_PANEL_IS_READY_EVENT, ASSIST_GET_DATABASE_EVENT, ASSIST_GET_SOURCE_EVENT, ASSIST_IS_DB_PANEL_READY_EVENT, ASSIST_SET_DATABASE_EVENT, ASSIST_SET_SOURCE_EVENT, ASSIST_SHOW_SOLR_EVENT, ASSIST_SHOW_SQL_EVENT, SHOW_LEFT_ASSIST_EVENT } from './events'; import { ASSIST_KEY_COMPONENT } from './ko.assistKey'; import dataCatalog from '../../../catalog/dataCatalog'; import { CONFIG_REFRESHED_TOPIC } from '../../../config/events'; import { filterEditorConnectors, findBrowserConnector, findDashboardConnector, findEditorConnector } from '../../../config/hueConfig'; import AssistDbSource from '../../../ko/components/assist/assistDbSource'; import componentUtils from '../../../ko/components/componentUtils'; import huePubSub from '../../../utils/huePubSub'; import I18n from '../../../utils/i18n'; import { getFromLocalStorage, setInLocalStorage } from '../../../utils/storageUtils'; const ASSIST_TABLE_TEMPLATES = ` <script type="text/html" id="assist-no-database-entries"> <ul class="assist-tables"> <li> <span class="assist-no-entries">${I18n('No entries found')}</span> </li> </ul> </script> <script type="text/html" id="assist-no-table-entries"> <ul> <li> <span class="assist-no-entries">${I18n('The table has no columns')}</span> </li> </ul> </script> <script type="text/html" id="sql-context-items"> <!-- ko if: typeof catalogEntry !== 'undefined' --> <li><a href="javascript:void(0);" data-bind="click: function (data) { showContextPopover(data, { target: $parentContext.$contextSourceElement }, { left: -15, top: 2 }); }"><i class="fa fa-fw fa-info"></i> ${I18n( 'Show details' )}</a></li> <!-- ko switch: sourceType --> <!-- ko case: 'solr' --> <!-- ko if: catalogEntry.isTableOrView() --> <li><a href="javascript:void(0);" data-bind="click: openInIndexer"><i class="fa fa-fw fa-table"></i> ${I18n( 'Open in Browser' )}</a></li> <li><a href="javascript: void(0);" data-bind="click: function() { explore(true); }"><!-- ko template: { name: 'app-icon-template', data: { icon: 'dashboard' } } --><!-- /ko --> ${I18n( 'Open in Dashboard' )}</a></li> <!-- /ko --> <!-- /ko --> <!-- ko case: $default --> <!-- ko if: !catalogEntry.isDatabase() && $currentApp() === 'editor' --> <li><a href="javascript:void(0);" data-bind="click: dblClick"><i class="fa fa-fw fa-paste"></i> ${I18n( 'Insert at cursor' )}</a></li> <!-- /ko --> <!-- ko if: catalogEntry.path.length <=2 --> <li><a href="javascript:void(0);" data-bind="click: openInMetastore"><i class="fa fa-fw fa-table"></i> ${I18n( 'Open in Browser' )}</a></li> <!-- /ko --> <!-- ko if: catalogEntry.isTableOrView() --> <li><a href="javascript:void(0);" data-bind="click: function() { huePubSub.publish('query.and.watch', {'url': '/notebook/browse/' + databaseName + '/' + tableName + '/', sourceType: sourceType}); }"><i class="fa fa-fw fa-code"></i> ${I18n( 'Open in Editor' )}</a></li> <!-- ko if: window.HAS_SQL_DASHBOARD --> <li><a href="javascript: void(0);" data-bind="click: function() { explore(false); }"><!-- ko template: { name: 'app-icon-template', data: { icon: 'dashboard' } } --><!-- /ko --> ${I18n( 'Open in Dashboard' )}</a></li> <!-- /ko --> <!-- /ko --> <!-- ko if: window.ENABLE_QUERY_BUILDER && catalogEntry.isColumn() && $currentApp() === 'editor' --> <li class="divider"></li> <!-- ko template: { name: 'query-builder-context-items' } --><!-- /ko --> <!-- /ko --> <!-- /ko --> <!-- /ko --> <!-- /ko --> </script> <script type="text/html" id="query-builder-context-items"> <li data-bind="contextSubMenu: '.hue-context-sub-menu'"> <a href="javascript:void(0);"><i class="fa fa-fw fa-magic"></i> ${I18n( 'Project' )}<i class="sub-icon fa fa-fw fa-chevron-right"></i></a> <ul class="hue-context-menu hue-context-sub-menu"> <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Select', 'Project', '{0}', false, false); }">Select</a></li> <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Select distinct', 'Project', '{0}', false, false); }">Select distinct</a></li> </ul> </li> <li data-bind="contextSubMenu: '.hue-context-sub-menu'"> <a href="javascript:void(0);"><i class="fa fa-fw fa-magic"></i> ${I18n( 'Aggregate' )}<i class="sub-icon fa fa-fw fa-chevron-right"></i></a> <ul class="hue-context-menu hue-context-sub-menu"> <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Count', 'Aggregate', 'COUNT({0}.{1}) as count_{1}', false, false); }">Count</a></li> <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Count distinct', 'Aggregate', 'COUNT(DISTINCT {0}.{1}) as distinct_count_{1}', false, false); }">Count distinct</a></li> <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Sum', 'Aggregate', 'SUM({0}.{1}) as sum_{1}', false, false); }">Sum</a></li> <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Minimum', 'Aggregate', 'MIN({0}.{1}) as min_{1}', false, false); }">Minimum</a></li> <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Maximum', 'Aggregate', 'MAX({0}.{1}) as max_{1}', false, false); }">Maximum</a></li> <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Average', 'Aggregate', 'AVG({0}.{1}) as avg_{1}', false, false); }">Average</a></li> </ul> </li> <li data-bind="contextSubMenu: '.hue-context-sub-menu'"> <a href="javascript:void(0);"><i class="fa fa-fw fa-magic"></i> ${I18n( 'Filter' )}<i class="sub-icon fa fa-fw fa-chevron-right"></i></a> <ul class="hue-context-menu hue-context-sub-menu"> <li><a href="javascript:void(0);" data-bind="click: function () { var isNotNullRule = QueryBuilder.getRule(databaseName, tableName, columnName, 'Is not null'); if (isNotNullRule.length) { isNotNullRule.attr('rule', 'Is null'); isNotNullRule.find('.rule').text('Is null'); } else { QueryBuilder.addRule(databaseName, tableName, columnName, 'Is null', 'Filter', '{0}.{1} = null', false, false); } }">Is null</a></li> <li><a href="javascript:void(0);" data-bind="click: function () { var isNullRule = QueryBuilder.getRule(databaseName, tableName, columnName, 'Is null'); if (isNullRule.length) { isNullRule.attr('rule', 'Is not null'); isNullRule.find('.rule').text('Is not null'); } else { QueryBuilder.addRule(databaseName, tableName, columnName, 'Is not null', 'Filter', '{0}.{1} != null', false, false); } }">Is not null</a></li> <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Equal to', 'Filter', '{0}.{1} = {2}', true, true); }">Equal to</a></li> <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Not equal to', 'Filter', '{0}.{1} != {2}', true, true); }">Not equal to</a></li> <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Greater than', 'Filter', '{0}.{1} > {2}', true, false) }">Greater than</a></li> <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Less than', 'Filter', '{0}.{1} < {2}', true, false); }">Less than</a></li> </ul> </li> <li data-bind="contextSubMenu: '.hue-context-sub-menu'"> <a href="javascript:void(0);"><i class="fa fa-fw fa-magic"></i> ${I18n( 'Order' )}<i class="sub-icon fa fa-fw fa-chevron-right"></i></a> <ul class="hue-context-menu hue-context-sub-menu"> <li><a href="javascript:void(0);" data-bind="click: function () { var descendingRule = QueryBuilder.getRule(databaseName, tableName, columnName, 'Descending'); if (descendingRule.length) { descendingRule.attr('rule', 'Ascending'); descendingRule.find('.rule').text('Ascending'); } else { QueryBuilder.addRule(databaseName, tableName, columnName, 'Ascending', 'Order', '{0}.{1} ASC', false, false); } }">Ascending</a></li> <li><a href="javascript:void(0);" data-bind="click: function () { var ascendingRule = QueryBuilder.getRule(databaseName, tableName, columnName, 'Ascending'); if (ascendingRule.length) { ascendingRule.attr('rule', 'Descending'); ascendingRule.find('.rule').text('Descending'); } else { QueryBuilder.addRule(databaseName, tableName, columnName, 'Descending', 'Order', '{0}.{1} DESC', false, false); } }">Descending</a></li> </ul> </li> </script> <script type="text/html" id="assist-table-entry"> <li class="assist-table" data-bind="appAwareTemplateContextMenu: { template: 'sql-context-items', scrollContainer: '.assist-db-scrollable' }, visibleOnHover: { override: statsVisible() || navigationSettings.rightAssist, selector: '.table-actions' }"> <div class="assist-actions table-actions" data-bind="css: { 'assist-actions-left': navigationSettings.rightAssist }" style="opacity: 0"> <a class="inactive-action" href="javascript:void(0)" data-bind="visible: navigationSettings.showStats, popoverOnHover: showContextPopover, css: { 'blue': statsVisible }"><i class="fa fa-fw fa-info" title="${I18n( 'Show details' )}"></i></a> <a class="inactive-action" href="javascript:void(0)" data-bind="visible: navigationSettings.openItem, click: openItem"><i class="fa fa-long-arrow-right" title="${I18n( 'Open' )}"></i></a> </div> <a class="assist-entry assist-table-link" href="javascript:void(0)" data-bind="click: toggleOpen, attr: {'title': catalogEntry.getTitle(true) }, draggableText: { text: editorText, meta: { connector: catalogEntry.getConnector(), type: 'sql', isView: catalogEntry.isView(), table: tableName, database: databaseName } }"> <i class="fa fa-fw muted valign-middle" data-bind="css: iconClass"></i> <span class="highlightable" data-bind="text: catalogEntry.getDisplayName(navigationSettings.rightAssist), css: { 'highlight': highlight }"></span> </a> <div class="center assist-spinner" data-bind="visible: loading() && open()"><i class="fa fa-spinner fa-spin"></i></div> <!-- ko template: { if: open, name: 'assist-db-entries' } --><!-- /ko --> </li> </script> <script type="text/html" id="assist-column-entry"> <li data-bind="appAwareTemplateContextMenu: { template: 'sql-context-items', scrollContainer: '.assist-db-scrollable' }, visible: ! hasErrors(), visibleOnHover: { childrenOnly: true, override: statsVisible, selector: catalogEntry.isView() ? '.table-actions' : '.column-actions' }, css: { 'assist-table': catalogEntry.isView(), 'assist-column': catalogEntry.isField() }"> <div class="assist-actions column-actions" data-bind="css: { 'assist-actions-left': navigationSettings.rightAssist }" style="opacity: 0"> <a class="inactive-action" href="javascript:void(0)" data-bind="visible: navigationSettings.showStats, popoverOnHover: showContextPopover, css: { 'blue': statsVisible }"><i class="fa fa-fw fa-info" title="${I18n( 'Show details' )}"></i></a> </div> <!-- ko if: expandable --> <a class="assist-entry assist-field-link" href="javascript:void(0)" data-bind="click: toggleOpen, attr: {'title': catalogEntry.getTitle(true) }, css: { 'assist-entry-left-action': navigationSettings.rightAssist }"> <span class="highlightable" data-bind="css: { 'highlight': highlight}, attr: {'column': columnName, 'table': tableName, 'database': databaseName }, text: catalogEntry.getDisplayName(), draggableText: { text: editorText, meta: { connector: catalogEntry.getConnector(), type: 'sql', column: columnName, table: tableName, database: databaseName } }"></span><!-- ko if: catalogEntry.isKey() --> <!-- ko component: { name: '${ASSIST_KEY_COMPONENT}', params: { entry: catalogEntry } } --><!-- /ko --><!-- /ko --> </a> <!-- /ko --> <!-- ko ifnot: expandable --> <div class="assist-entry assist-field-link default-cursor" href="javascript:void(0)" data-bind="event: { dblclick: dblClick }, attr: {'title': catalogEntry.getTitle(true) }, css: { 'assist-entry-left-action': navigationSettings.rightAssist }"> <span class="highlightable" data-bind="css: { 'highlight': highlight}, attr: {'column': columnName, 'table': tableName, 'database': databaseName}, text: catalogEntry.getDisplayName(), draggableText: { text: editorText, meta: { connector: catalogEntry.getConnector(), type: 'sql', column: columnName, table: tableName, database: databaseName } }"></span><!-- ko if: catalogEntry.isKey() --> <!-- ko component: { name: '${ASSIST_KEY_COMPONENT}', params: { entry: catalogEntry } } --><!-- /ko --><!-- /ko --> </div> <!-- /ko --> <div class="center assist-spinner" data-bind="visible: loading"><i class="fa fa-spinner fa-spin"></i></div> <!-- ko template: { if: open, name: 'assist-db-entries' } --><!-- /ko --> </li> </script> <script type="text/html" id="assist-column-entry-assistant"> <li data-bind="appAwareTemplateContextMenu: { template: 'sql-context-items', scrollContainer: '.assist-db-scrollable' }, visible: ! hasErrors(), visibleOnHover: { childrenOnly: true, override: statsVisible, selector: catalogEntry.isView() ? '.table-actions' : '.column-actions' }, css: { 'assist-table': catalogEntry.isView(), 'assist-column': catalogEntry.isField() }"> <div class="assist-actions column-actions assist-actions-left" style="opacity: 0"> <a class="inactive-action" href="javascript:void(0)" data-bind="visible: navigationSettings.showStats, popoverOnHover: showContextPopover, css: { 'blue': statsVisible }"><i class="fa fa-fw fa-info" title="${I18n( 'Show details' )}"></i></a> </div> <!-- ko if: expandable --> <a class="assist-entry assist-field-link assist-field-link-dark assist-entry-left-action assist-ellipsis" href="javascript:void(0)" data-bind="click: toggleOpen, attr: {'title': catalogEntry.getTitle(true) }"> <span data-bind="text: catalogEntry.getType()" class="muted pull-right margin-right-20"></span> <span class="highlightable" data-bind="css: { 'highlight': highlight}, attr: { 'column': columnName, 'table': tableName, 'database': databaseName }, text: catalogEntry.name, draggableText: { text: editorText, meta: { connector: catalogEntry.getConnector(), type: 'sql', column: columnName, table: tableName, database: databaseName } }"></span><!-- ko if: catalogEntry.isKey() --> <!-- ko component: { name: '${ASSIST_KEY_COMPONENT}', params: { entry: catalogEntry } } --><!-- /ko --><!-- /ko --> </a> <!-- /ko --> <!-- ko ifnot: expandable --> <div class="assist-entry assist-field-link assist-field-link-dark default-cursor assist-ellipsis" href="javascript:void(0)" data-bind="event: { dblclick: dblClick }, attr: {'title': catalogEntry.getTitle(true) }, css: { 'assist-entry-left-action': navigationSettings.rightAssist }"> <span data-bind="text: catalogEntry.getType()" class="muted pull-right margin-right-20"></span> <span class="highlightable" data-bind="css: { 'highlight': highlight}, attr: {'column': columnName, 'table': tableName, 'database': databaseName}, text: catalogEntry.name, draggableText: { text: editorText, meta: { connector: catalogEntry.getConnector(), type: 'sql', column: columnName, table: tableName, database: databaseName } }"></span><!-- ko if: catalogEntry.isKey() --> <!-- ko component: { name: '${ASSIST_KEY_COMPONENT}', params: { entry: catalogEntry } } --><!-- /ko --><!-- /ko --> </div> <!-- /ko --> <div class="center assist-spinner" data-bind="visible: loading"><i class="fa fa-spinner fa-spin"></i></div> <!-- ko template: { if: open, name: 'assist-db-entries' } --><!-- /ko --> </li> </script> <script type="text/html" id="assist-db-entries"> <!-- ko if: ! hasErrors() && hasEntries() && ! loading() && filteredEntries().length == 0 --> <ul class="assist-tables"> <li class="assist-entry assist-no-entries"><!-- ko if: catalogEntry.isTableOrView() -->${I18n( 'No columns found' )}<!--/ko--><!-- ko if: catalogEntry.isDatabase() -->${I18n( 'No tables found' )}<!--/ko--><!-- ko if: catalogEntry.isField() -->${I18n('No results found')}<!--/ko--></li> </ul> <!-- /ko --> <!-- ko if: ! hasErrors() && hasEntries() && ! loading() && filteredEntries().length > 0 --> <ul class="database-tree" data-bind="foreachVisible: { data: filteredEntries, minHeight: navigationSettings.rightAssist ? 22 : 23, container: '.assist-db-scrollable', skipScrollEvent: navigationSettings.rightAssist, usePreloadBackground: true }, css: { 'assist-tables': catalogEntry.isDatabase() }"> <!-- ko template: { if: catalogEntry.isTableOrView(), name: 'assist-table-entry' } --><!-- /ko --> <!-- ko if: navigationSettings.rightAssist --> <!-- ko template: { ifnot: catalogEntry.isTableOrView(), name: 'assist-column-entry-assistant' } --><!-- /ko --> <!-- /ko --> <!-- ko ifnot: navigationSettings.rightAssist --> <!-- ko template: { ifnot: catalogEntry.isTableOrView(), name: 'assist-column-entry' } --><!-- /ko --> <!-- /ko --> </ul> <!-- /ko --> <!-- ko template: { if: ! hasErrors() && ! hasEntries() && ! loading() && (catalogEntry.isTableOrView()), name: 'assist-no-table-entries' } --><!-- /ko --> <!-- ko template: { if: ! hasErrors() && ! hasEntries() && ! loading() && catalogEntry.isDatabase(), name: 'assist-no-database-entries' } --><!-- /ko --> <!-- ko if: hasErrors --> <ul class="assist-tables"> <!-- ko if: catalogEntry.isTableOrView() --> <li class="assist-errors">${I18n('Error loading columns.')}</li> <!-- /ko --> <!-- ko if: catalogEntry.isField() --> <li class="assist-errors">${I18n('Error loading fields.')}</li> <!-- /ko --> </ul> <!-- /ko --> </script> `; const TEMPLATE = ASSIST_TABLE_TEMPLATES + ` <script type="text/html" id="assist-database-actions"> <div class="assist-actions database-actions" style="opacity: 0"> <!-- ko if: sourceType === 'hive' || sourceType === 'impala' --> <a class="inactive-action" href="javascript:void(0)" data-bind="visible: navigationSettings.showStats, popoverOnHover: function (data, event) { showContextPopover(data, event); }, css: { 'blue': statsVisible }"><i class="fa fa-fw fa-info" title="${I18n( 'Show details' )}"></i></a> <!-- /ko --> <a class="inactive-action" href="javascript:void(0)" data-bind="visible: navigationSettings.openItem, click: openItem"><i class="fa fa-long-arrow-right" title="${I18n( 'Open' )}"></i></a> </div> </script> <script type="text/html" id="collection-title-context-items"> <li><a href="javascript:void(0);" data-bind="hueLink: '/indexer'"><i class="fa fa-fw fa-table"></i> ${I18n( 'Open in Browser' )}</a></li> </script> <script type="text/html" id="assist-database-entry"> <li class="assist-table" data-bind="appAwareTemplateContextMenu: { template: 'sql-context-items', scrollContainer: '.assist-db-scrollable' }, visibleOnHover: { selector: '.database-actions' }"> <!-- ko template: { name: 'assist-database-actions' } --><!-- /ko --> <a class="assist-table-link" href="javascript: void(0);" data-bind="click: function () { $parent.selectedDatabase($data); $parent.selectedDatabaseChanged(); }, attr: {'title': catalogEntry.getTitle(true) }, draggableText: { text: editorText, meta: { connector: catalogEntry.getConnector(), type: 'sql', database: databaseName } }"><i class="fa fa-fw fa-database muted valign-middle"></i> <span class="highlightable" data-bind="text: catalogEntry.name, css: { 'highlight': highlight() }"></span></a> </li> </script> <script type="text/html" id="assist-db-breadcrumb"> <div class="assist-flex-header assist-breadcrumb"> <!-- ko if: selectedSource() --> <!-- ko if: selectedSource().selectedNamespace() --> <!-- ko if: selectedSource().selectedNamespace().selectedDatabase() --> <a data-bind="click: back, appAwareTemplateContextMenu: { template: 'sql-context-items', viewModel: selectedSource().selectedNamespace().selectedDatabase() }"> <i class="fa fa-chevron-left assist-breadcrumb-back" ></i> <i class="fa assist-breadcrumb-text" data-bind="css: { 'fa-server': nonSqlType, 'fa-database': !nonSqlType }"></i> <span class="assist-breadcrumb-text" data-bind="text: breadcrumb, attr: {'title': breadcrumbTitle }"></span> </a> <!-- /ko --> <!-- ko ifnot: selectedSource().selectedNamespace().selectedDatabase() --> <!-- ko if: window.HAS_MULTI_CLUSTER--> <a data-bind="click: back"> <i class="fa fa-chevron-left assist-breadcrumb-back"></i> <i class="fa fa-snowflake-o assist-breadcrumb-text"></i> <span class="assist-breadcrumb-text" data-bind="text: breadcrumb, attr: {'title': breadcrumbTitle }"></span> </a> <!-- /ko --> <!-- ko ifnot: window.HAS_MULTI_CLUSTER --> <a data-bind="click: back"> <i class="fa fa-chevron-left assist-breadcrumb-back"></i> <i class="fa fa-server assist-breadcrumb-text"></i> <span class="assist-breadcrumb-text" data-bind="text: breadcrumb"></span> </a> <!-- /ko --> <!-- /ko --> <!-- /ko --> <!-- ko ifnot: selectedSource().selectedNamespace() --> <a data-bind="click: back"> <i class="fa fa-chevron-left assist-breadcrumb-back"></i> <i class="fa fa-server assist-breadcrumb-text"></i> <span class="assist-breadcrumb-text" data-bind="text: breadcrumb"></span> </a> <!-- /ko --> <!-- /ko --> </div> </script> <script type="text/html" id="assist-sql-inner-panel"> <!-- ko template: { if: breadcrumb() !== null, name: 'assist-db-breadcrumb' } --><!-- /ko --> <!-- ko template: { ifnot: selectedSource, name: 'assist-sources-template' } --><!-- /ko --> <!-- ko with: selectedSource --> <!-- ko template: { ifnot: selectedNamespace, name: 'assist-namespaces-template' } --><!-- /ko --> <!-- ko with: selectedNamespace --> <!-- ko template: { ifnot: selectedDatabase, name: 'assist-databases-template' } --><!-- /ko --> <!-- ko with: selectedDatabase --> <!-- ko template: { name: 'assist-tables-template' } --><!-- /ko --> <!-- /ko --> <!-- /ko --> <!-- /ko --> </script> <script type="text/html" id="ask-for-invalidate-title"> &nbsp;<a class="pull-right pointer close-popover inactive-action">&times;</a> </script> <script type="text/html" id="assist-namespace-header-actions"> <div class="assist-db-header-actions"> <!-- ko ifnot: loading --> <span class="assist-tables-counter">(<span data-bind="text: filteredNamespaces().length"></span>)</span> <!-- ko if: window.HAS_MULTI_CLUSTER --> <a class="inactive-action" href="javascript:void(0)" data-bind="click: triggerRefresh"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin blue' : loading }" title="${I18n( 'Refresh' )}"></i></a> <!-- /ko --> <!-- /ko --> <!-- ko if: loading --> <i class="fa fa-refresh fa-spin blue" title="${I18n('Refresh')}"></i> <!-- /ko --> </div> </script> <script type="text/html" id="assist-db-header-actions"> <div class="assist-db-header-actions"> <!-- ko ifnot: loading --> <span class="assist-tables-counter">(<span data-bind="text: filteredEntries().length"></span>)</span> <!-- ko if: sourceType !== 'solr' && $component.showImporter() --> <!-- ko if: typeof databaseName !== 'undefined' --> <a class="inactive-action" data-bind="hueLink: importer_url + databaseName + '/?sourceType=' + sourceType + '&namespace=' + assistDbNamespace.namespace.id" title="${I18n( 'Create table' )}" href="javascript:void(0)"> <i class="pointer fa fa-plus" title="${I18n('Create table')}"></i> </a> <!-- /ko --> <!-- ko if: typeof databases !== 'undefined' --> <a class="inactive-action" data-bind="hueLink: window.HUE_URLS.IMPORTER_CREATE_DATABASE + '/?sourceType=' + sourceType + '&namespace=' + namespace.id" href="javascript:void(0)"> <i class="pointer fa fa-plus" title="${I18n('Create database')}"></i> </a> <!-- /ko --> <!-- /ko --> <!-- ko if: sourceType === 'solr' --> <a class="inactive-action" data-bind="hueLink: '/indexer/importer/prefill/all/index/'" title="${I18n( 'Create index' )}"> <i class="pointer fa fa-plus" title="${I18n('Create index')}"></i> </a> <!-- /ko --> <a class="inactive-action" href="javascript:void(0)" data-bind="click: triggerRefresh"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin blue' : loading }" title="${I18n( 'Refresh' )}"></i></a> <!-- /ko --> <!-- ko if: loading --> <i class="fa fa-refresh fa-spin blue" title="${I18n('Refresh')}"></i> <!-- /ko --> </div> </script> <script type="text/html" id="assist-sources-template"> <div class="assist-flex-header"> <div class="assist-inner-header"> ${I18n('Sources')} </div> </div> <div class="assist-flex-fill"> <ul class="assist-tables" data-bind="foreach: sources"> <li class="assist-table"> <a class="assist-table-link" href="javascript: void(0);" data-bind="click: function () { $parent.selectedSource($data); }"><i class="fa fa-fw fa-server muted valign-middle"></i> <span data-bind="text: name"></span></a> </li> </ul> </div> </script> <script type="text/html" id="assist-namespaces-template"> <div class="assist-flex-header"> <div class="assist-inner-header"> ${I18n('Namespaces')} <!-- ko template: 'assist-namespace-header-actions' --><!-- /ko --> </div> </div> <div class="assist-flex-search" data-bind="visible: hasNamespaces() && !hasErrors()"> <div class="assist-filter"> <!-- ko component: { name: 'inline-autocomplete', params: { querySpec: filter.querySpec, facets: [], knownFacetValues: {}, autocompleteFromEntries: autocompleteFromNamespaces } } --><!-- /ko --> </div> </div> <div class="assist-flex-fill assist-db-scrollable" data-bind="visible: !hasErrors() && !loading() && hasNamespaces(), delayedOverflow"> <!-- ko if: !loading() && filteredNamespaces().length == 0 --> <ul class="assist-tables"> <li class="assist-entry no-entries">${I18n('No results found')}</li> </ul> <!-- /ko --> <ul class="assist-tables" data-bind="foreach: filteredNamespaces"> <li class="assist-table"> <!-- ko if: status() === 'STARTING' --> <span class="assist-table-link" title="${I18n( 'Starting' )}" data-bind="tooltip: { placement: 'bottom' }"><i class="fa fa-fw fa-spinner fa-spin muted valign-middle"></i> <span data-bind="text: name"></span></span> <!-- /ko --> <!-- ko if: status() !== 'STARTING' --> <!-- ko if: namespace.computes.length --> <a class="assist-table-link" href="javascript: void(0);" data-bind="click: function () { $parent.selectedNamespace($data); }"><i class="fa fa-fw fa-snowflake-o muted valign-middle"></i> <span data-bind="text: name"></span></a> <!-- /ko --> <!-- ko ifnot: namespace.computes.length --> <span class="assist-table-link" title="${I18n( 'No related computes' )}" data-bind="tooltip: { placement: 'bottom' }"><i class="fa fa-fw fa-warning muted valign-middle"></i> <span data-bind="text: name"></span></span> <!-- /ko --> <!-- /ko --> </li> </ul> </div> <div class="assist-flex-fill" data-bind="visible: loading"> <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko --> </div> <div class="assist-flex-fill" data-bind="visible: hasErrors() && !loading()"> <span class="assist-errors">${I18n('Error loading namespaces.')}</span> </div> <div class="assist-flex-fill" data-bind="visible: !hasErrors() && !loading() && !hasNamespaces()"> <span class="assist-errors">${I18n('No namespaces found.')}</span> </div> </script> <script type="text/html" id="assist-databases-template"> <div class="assist-flex-header" data-bind="visibleOnHover: { selector: '.hover-actions', override: loading() }"> <div class="assist-inner-header"> <!-- ko ifnot: sourceType === 'solr' || sourceType === 'kafka' --> ${I18n('Databases')} <!-- ko template: 'assist-db-header-actions' --><!-- /ko --> <!-- /ko --> <!-- ko if: sourceType === 'solr' || sourceType === 'kafka'--> ${I18n('Sources')} <!-- /ko --> </div> </div> <div class="assist-flex-search" data-bind="visible: hasEntries() && ! hasErrors()"> <div class="assist-filter"> <!-- ko component: { name: 'inline-autocomplete', params: { querySpec: filter.querySpec, facets: [], placeHolder: sourceType === 'solr' || sourceType === 'kafka' ? '${I18n( 'Filter sources...' )}' : '${I18n('Filter databases...')}', knownFacetValues: {}, autocompleteFromEntries: autocompleteFromEntries } } --><!-- /ko --> </div> </div> <div class="assist-flex-fill assist-db-scrollable" data-bind="visible: ! hasErrors() && ! loading() && hasEntries(), delayedOverflow"> <!-- ko if: ! loading() && filteredEntries().length == 0 --> <ul class="assist-tables"> <li class="assist-entry no-entries">${I18n('No results found')}</li> </ul> <!-- /ko --> <ul class="assist-tables" data-bind="foreachVisible: {data: filteredEntries, minHeight: 23, container: '.assist-db-scrollable', skipScrollEvent: navigationSettings.rightAssist }"> <!-- ko template: { name: 'assist-database-entry' } --><!-- /ko --> </ul> </div> <div class="assist-flex-fill" data-bind="visible: loading"> <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko --> </div> <div class="assist-flex-fill" data-bind="visible: hasErrors() && ! loading()"> <span class="assist-errors">${I18n('Error loading databases.')}</span> </div> <div class="assist-flex-fill" data-bind="visible: ! hasErrors() && ! loading() && ! hasEntries()"> <span class="assist-errors">${I18n('No databases found.')}</span> </div> </script> <script type="text/html" id="assist-tables-template"> <div class="assist-flex-header"> <div class="assist-inner-header" data-bind="visible: !$parent.loading() && !$parent.hasErrors()"> <!-- ko ifnot: sourceType === 'solr' || sourceType === 'kafka' --> ${I18n('Tables')} <!-- /ko --> <!-- ko if: sourceType === 'solr' --> <div data-bind="appAwareTemplateContextMenu: { template: 'collection-title-context-items', scrollContainer: '.assist-db-scrollable' }">${I18n( 'Indexes' )}</div> <!-- /ko --> <!-- ko if: sourceType === 'kafka' --> ${I18n('Topics')} <!-- /ko --> <!-- ko template: 'assist-db-header-actions' --><!-- /ko --> </div> </div> <div class="assist-flex-search" data-bind="visible: hasEntries() && !$parent.loading() && !$parent.hasErrors()"> <div class="assist-filter"> <!-- ko component: { name: 'inline-autocomplete', params: { querySpec: filter.querySpec, facets: ['type'], knownFacetValues: knownFacetValues.bind($data), autocompleteFromEntries: autocompleteFromEntries } } --><!-- /ko --> </div> </div> <div class="assist-flex-fill assist-db-scrollable" data-bind="visible: ! hasErrors() && ! loading(), delayedOverflow"> <!-- ko template: 'assist-db-entries' --><!-- /ko --> </div> <div class="assist-flex-fill" data-bind="visible: loading() || $parent.loading()"> <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko --> </div> <div class="assist-flex-fill" data-bind="visible: hasErrors() && ! loading()"> <span class="assist-errors">${I18n('Error loading tables.')}</span> </div> </script> <!-- ko template: 'assist-sql-inner-panel' --><!-- /ko --> `; class AssistDbPanel { /** * @param {Object} options * @param {Object} options.i18n * @param {Object} options.navigationSettings - enable/disable the links * @param {boolean} options.navigationSettings.openItem * @param {boolean} options.navigationSettings.showStats * @param {boolean} options.navigationSettings.pinEnabled * @param {boolean} [options.isSolr] - Default false; * @param {boolean} [options.isStreams] - Default false; * @constructor **/ constructor(options) { this.options = options; this.i18n = options.i18n; this.isStreams = options.isStreams; this.isSolr = options.isSolr; this.nonSqlType = this.isSolr || this.isStreams; this.showImporter = ko.observable(false); this.sources = ko.observableArray(); this.sourceIndex = {}; this.selectedSource = ko.observable(null); this.breadcrumbTitle = ko.pureComputed( () => `${this.breadcrumb()} (${this.selectedSource().connector.displayName})` ); this.breadcrumb = ko.pureComputed(() => { if (this.isStreams && this.selectedSource()) { return this.selectedSource().connector.displayName; } if (!this.isSolr && this.selectedSource()) { if (this.selectedSource().selectedNamespace()) { if (this.selectedSource().selectedNamespace().selectedDatabase()) { return this.selectedSource().selectedNamespace().selectedDatabase().catalogEntry.name; } if (window.HAS_MULTI_CLUSTER) { return this.selectedSource().selectedNamespace().name; } } return this.selectedSource().connector.displayName; } return null; }); huePubSub.subscribe(ASSIST_DB_HIGHLIGHT_EVENT, catalogEntry => { huePubSub.publish(SHOW_LEFT_ASSIST_EVENT); if (catalogEntry.getDialect() === 'solr') { huePubSub.publish(ASSIST_SHOW_SOLR_EVENT); } else { huePubSub.publish(ASSIST_SHOW_SQL_EVENT); } huePubSub.publish('context.popover.hide'); window.setTimeout(() => { let foundSource; this.sources().some(source => { if (source.sourceType === catalogEntry.getConnector().id) { foundSource = source; return true; } }); if (foundSource) { if (this.selectedSource() !== foundSource) { this.selectedSource(foundSource); } foundSource.highlightInside(catalogEntry); } }, 0); }); if (this.isSolr) { huePubSub.subscribe('assist.collections.refresh', () => { const solrSource = this.sourceIndex['solr']; const doRefresh = () => { if (solrSource.selectedNamespace()) { const assistDbNamespace = solrSource.selectedNamespace(); dataCatalog .getEntry({ namespace: assistDbNamespace.namespace, compute: assistDbNamespace.compute(), connector: { id: 'solr' }, path: [] }) .then(entry => { entry.clearCache({ cascade: true }); }); } }; if (!solrSource.hasNamespaces()) { solrSource.loadNamespaces(true).done(doRefresh); } else { doRefresh(); } }); } else if (!this.isSolr && !this.isStreams) { huePubSub.subscribe(ASSIST_SET_DATABASE_EVENT, databaseDef => { if (!databaseDef.connector || !this.sourceIndex[databaseDef.connector.id]) { return; } this.selectedSource(this.sourceIndex[databaseDef.connector.id]); this.setDatabaseWhenLoaded(databaseDef.namespace, databaseDef.name); }); const getSelectedDatabase = connector => { const deferred = $.Deferred(); const assistDbSource = this.sourceIndex[connector.id]; if (assistDbSource) { assistDbSource.loadedDeferred.done(() => { if (assistDbSource.selectedNamespace()) { assistDbSource.selectedNamespace().loadedDeferred.done(() => { if (assistDbSource.selectedNamespace().selectedDatabase()) { deferred.resolve( assistDbSource.selectedNamespace().selectedDatabase().catalogEntry ); } else { let lastSelectedDb = getFromLocalStorage( 'assist_' + connector.id + '_' + assistDbSource.selectedNamespace().namespace.id + '.lastSelectedDb' ); if (!lastSelectedDb && lastSelectedDb !== '') { lastSelectedDb = 'default'; } dataCatalog .getEntry({ connector: connector, namespace: assistDbSource.selectedNamespace().namespace, compute: assistDbSource.selectedNamespace().compute, path: [lastSelectedDb] }) .then(deferred.resolve); } }); } else { dataCatalog .getEntry({ connector: connector, namespace: { id: 'default' }, compute: { id: 'default' }, path: ['default'] }) .then(deferred.resolve); } }); } else { deferred.reject(); } return deferred; }; huePubSub.subscribe(ASSIST_GET_DATABASE_EVENT, options => { getSelectedDatabase(options.connector).done(options.callback); }); huePubSub.subscribe(ASSIST_GET_SOURCE_EVENT, callback => { callback(this.selectedSource() ? this.selectedSource().sourceType : undefined); }); huePubSub.subscribe(ASSIST_SET_SOURCE_EVENT, source => { if (this.sourceIndex[source]) { this.selectedSource(this.sourceIndex[source]); } }); this.selectedSource.subscribe(newSource => { if (newSource) { if (newSource.namespaces().length === 0) { newSource.loadNamespaces(); } setInLocalStorage('assist.lastSelectedSource', newSource.sourceType); } else { setInLocalStorage('assist.lastSelectedSource', null); } }); } this.init(options.navigationSettings); huePubSub.publish(ASSIST_DB_PANEL_IS_READY_EVENT); huePubSub.subscribe(ASSIST_IS_DB_PANEL_READY_EVENT, () => { huePubSub.publish(ASSIST_DB_PANEL_IS_READY_EVENT); }); } setDatabaseWhenLoaded(namespace, databaseName) { this.selectedSource().whenLoaded(() => { if ( this.selectedSource().selectedNamespace() && this.selectedSource().selectedNamespace().namespace.id !== namespace.id ) { this.selectedSource() .namespaces() .some(otherNamespace => { if (otherNamespace.namespace.id === namespace.id) { this.selectedSource().selectedNamespace(otherNamespace); return true; } }); } if (this.selectedSource().selectedNamespace()) { this.selectedSource() .selectedNamespace() .whenLoaded(() => { this.selectedSource().selectedNamespace().setDatabase(databaseName); }); } }); } back() { if (this.isStreams) { this.selectedSource(null); return; } if (this.selectedSource()) { if (this.selectedSource() && this.selectedSource().selectedNamespace()) { if (this.selectedSource().selectedNamespace().selectedDatabase()) { this.selectedSource().selectedNamespace().selectedDatabase(null); this.selectedSource().selectedNamespace().selectedDatabaseChanged(); } else if (window.HAS_MULTI_CLUSTER) { this.selectedSource().selectedNamespace(null); } else { this.selectedSource(null); } } else { this.selectedSource(null); } } } setSingleSource(connector, navigationSettings, nonSqlType) { this.sourceIndex = {}; const source = new AssistDbSource({ i18n: this.i18n, type: connector.id, name: connector.displayName, connector: connector, nonSqlType: nonSqlType, navigationSettings: navigationSettings }); this.sourceIndex[connector.id] = source; this.sources([source]); this.selectedSource(source); source.loadNamespaces().then(() => { const namespace = this.selectedSource().selectedNamespace(); if (namespace) { namespace.initDatabases(); namespace.whenLoaded(() => { namespace.setDatabase('default'); }); } }); } init(navigationSettings) { if (this.isSolr) { const connector = findDashboardConnector(connector => connector.id === 'solr'); this.setSingleSource(connector, navigationSettings, true); return; } if (this.isStreams) { const connector = findEditorConnector(connector => connector.dialect === 'ksql'); this.setSingleSource(connector, navigationSettings, true); return; } const updateFromConfig = () => { const sources = []; this.showImporter(!!findBrowserConnector(connector => connector.type === 'importer')); const connectors = filterEditorConnectors(connector => connector.is_sql); connectors.forEach(connector => { const source = this.sourceIndex[connector.id] || new AssistDbSource({ i18n: this.i18n, type: connector.id, // TODO: Remove redundant name: connector.displayName, // TODO: Remove redundant connector: connector, nonSqlType: false, navigationSettings: navigationSettings }); sources.push(source); }); this.sourceIndex = {}; sources.forEach(source => { this.sourceIndex[source.sourceType] = source; }); if (sources.indexOf(this.selectedSource()) === -1) { if (sources.length) { const storageSourceType = getFromLocalStorage('assist.lastSelectedSource'); this.selectedSource( (storageSourceType && this.sourceIndex[storageSourceType]) || sources[0] ); } else { this.selectedSource(undefined); } } this.sources(sources); }; huePubSub.subscribe(CONFIG_REFRESHED_TOPIC, updateFromConfig); updateFromConfig(); } } const instances = {}; const viewModelFactory = { createViewModel: params => { const name = params.isStreams ? 'streams' : params.isSolr ? 'solr' : 'sql'; if (!instances[name]) { instances[name] = new AssistDbPanel(params); } return instances[name]; } }; componentUtils.registerComponent('hue-assist-db-panel', viewModelFactory, TEMPLATE); export default ASSIST_TABLE_TEMPLATES;