UNPKG

gethue

Version:

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

571 lines (517 loc) 17.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 sqlAnalyzerRepository from '../../../catalog/analyzer/sqlAnalyzerRepository'; import $ from 'jquery'; import * as ko from 'knockout'; import huePubSub from '../../../utils/huePubSub'; import sqlUtils from '../../../sql/sqlUtils'; import { findEditorConnector } from '../../../config/hueConfig'; const findNameInHierarchy = async (entry, searchCondition) => { while (entry && !searchCondition(entry)) { entry = entry.parent; } if (entry) { return await sqlUtils.backTickIfNeeded( entry.catalogEntry.getConnector(), entry.catalogEntry.name ); } }; class AssistDbEntry { /** * @param {DataCatalogEntry} catalogEntry * @param {AssistDbEntry} parent * @param {AssistDbNamespace} assistDbNamespace * @param {Object} filter * @param {function} filter.querySpec (observable) * @param {Object} i18n * @param {string} i18n.errorLoadingTablePreview * @param {Object} navigationSettings * @constructor */ constructor(catalogEntry, parent, assistDbNamespace, filter, i18n, navigationSettings) { const self = this; self.catalogEntry = catalogEntry; self.parent = parent; self.assistDbNamespace = assistDbNamespace; self.filter = filter; self.i18n = i18n; self.navigationSettings = navigationSettings; self.sourceType = assistDbNamespace.sourceType; self.phoenix_connector = findEditorConnector(connector => connector.dialect === 'phoenix'); self.importer_url = self.phoenix_connector && self.sourceType === self.phoenix_connector.id ? window.HUE_URLS.IMPORTER_CREATE_PHOENIX_TABLE : window.HUE_URLS.IMPORTER_CREATE_TABLE; self.expandable = self.catalogEntry.hasPossibleChildren(); self.filterColumnNames = ko.observable(false); self.highlight = ko.observable(false); self.popularity = ko.observable(0); self.loaded = false; self.loading = ko.observable(false); self.open = ko.observable(false); self.entries = ko.observableArray([]); self.statsVisible = ko.observable(false); self.hasErrors = ko.observable(false); self.iconClass = ''; if (!self.navigationSettings.rightAssist) { if (self.sourceType === 'solr') { self.iconClass = 'fa-search'; } else if (self.sourceType === 'kafka') { self.iconClass = 'fa-sitemap'; } else if (self.catalogEntry.isView()) { self.iconClass = 'fa-eye'; } else if (self.catalogEntry.isModel()) { self.iconClass = 'fa-puzzle-piece'; } else { self.iconClass = 'fa-table'; } } self.open.subscribe(newValue => { if (newValue && self.entries().length === 0) { self.loadEntries(); } }); self.hasEntries = ko.pureComputed(() => self.entries().length > 0); self.filteredEntries = ko.pureComputed(() => { const facets = self.filter.querySpec().facets; const facetMatch = !facets || Object.keys(facets).length === 0 || !facets['type']; // So far only type facet is used for SQL // Only text match on tables/views or columns if flag is set const textMatch = (!self.catalogEntry.isDatabase() && !self.filterColumnNames()) || !self.filter.querySpec().text || self.filter.querySpec().text.length === 0; if (facetMatch && textMatch) { return self.entries(); } return self.entries().filter(entry => { let match = true; if (match && !facetMatch) { if (entry.catalogEntry.isField()) { match = (Object.keys(facets['type']).length === 2 && facets['type']['table'] && facets['type']['view']) || (Object.keys(facets['type']).length === 1 && (facets['type']['table'] || facets['type']['view'])) || facets['type'][entry.catalogEntry.getType()]; } else if (entry.catalogEntry.isTableOrView()) { match = (!facets['type']['table'] && !facets['type']['view']) || (facets['type']['table'] && entry.catalogEntry.isTable()) || (facets['type']['view'] && entry.catalogEntry.isView()); } } if (match && !textMatch) { const nameLower = entry.catalogEntry.name.toLowerCase(); match = self.filter .querySpec() .text.every(text => nameLower.indexOf(text.toLowerCase()) !== -1); } return match; }); }); self.autocompleteFromEntries = (nonPartial, partial) => { const result = []; const partialLower = partial.toLowerCase(); self.entries().forEach(entry => { if (entry.catalogEntry.name.toLowerCase().indexOf(partialLower) === 0) { result.push(nonPartial + partial + entry.catalogEntry.name.substring(partial.length)); } }); return result; }; self.tableName = null; self.columnName = null; self.type = null; self.databaseName = self.getHierarchy()[0]; if (self.catalogEntry.isTableOrView()) { self.tableName = self.catalogEntry.name; self.columnName = null; self.type = self.catalogEntry.getType(); } else if (self.catalogEntry.isColumn()) { self.tableName = parent.catalogEntry.name; self.columnName = self.catalogEntry.name; } self.editorText = ko.observable(); const setEditorText = async () => { if (self.catalogEntry.isTableOrView()) { self.editorText(await self.getTableName()); } else if (self.catalogEntry.isColumn()) { self.editorText((await self.getColumnName()) + ', '); } else { self.editorText((await self.getComplexName()) + ', '); } }; setEditorText(); } knownFacetValues() { const self = this; const types = {}; if (self.parent === null) { // Only find facets on the DB level self.entries().forEach(tableEntry => { if (!self.assistDbNamespace.nonSqlType) { if (tableEntry.catalogEntry.isTable()) { types.table = types.table ? types.table + 1 : 1; } else if (tableEntry.catalogEntry.isView()) { types.view = types.view ? types.view + 1 : 1; } } if (tableEntry.open()) { tableEntry.entries().forEach(colEntry => { if (!types[colEntry.catalogEntry.getType()]) { types[colEntry.catalogEntry.getType()] = 1; } else { types[colEntry.catalogEntry.getType()]++; } }); } }); } if (Object.keys(types).length) { return { type: types }; } return {}; } async getDatabaseName() { return await findNameInHierarchy(this, entry => entry.catalogEntry.isDatabase()); } async getTableName() { return await findNameInHierarchy(this, entry => entry.catalogEntry.isTableOrView()); } async getColumnName() { return await findNameInHierarchy(this, entry => entry.catalogEntry.isColumn()); } async getComplexName() { let entry = this; const sourceType = entry.sourceType; const parts = []; while (entry != null) { if (entry.catalogEntry.isTableOrView()) { break; } if (entry.catalogEntry.isArray() || entry.catalogEntry.isMapValue()) { if (sourceType === 'hive') { parts.push('[]'); } } else { parts.push( await sqlUtils.backTickIfNeeded( entry.catalogEntry.getConnector(), entry.catalogEntry.name ) ); parts.push('.'); } entry = entry.parent; } parts.reverse(); return parts.slice(1).join(''); } showContextPopover(entry, event, positionAdjustment) { const self = this; const $source = $(event.target); const offset = $source.offset(); if (positionAdjustment) { offset.left += positionAdjustment.left; offset.top += positionAdjustment.top; } self.statsVisible(true); huePubSub.publish('context.popover.show', { data: { type: 'catalogEntry', catalogEntry: self.catalogEntry }, showInAssistEnabled: !!self.navigationSettings.rightAssist, orientation: self.navigationSettings.rightAssist ? 'left' : 'right', pinEnabled: self.navigationSettings.pinEnabled, source: { element: event.target, left: offset.left, top: offset.top - 3, right: offset.left + $source.width() + 1, bottom: offset.top + $source.height() - 3 } }); huePubSub.subscribeOnce('context.popover.hidden', () => { self.statsVisible(false); }); } triggerRefresh() { const self = this; self.catalogEntry.clearCache({ cascade: true }); } highlightInside(path) { const self = this; const searchEntry = () => { let foundEntry; self.entries().forEach(entry => { entry.highlight(false); if (entry.catalogEntry.name === path[0]) { foundEntry = entry; } }); if (foundEntry) { if (foundEntry.expandable && !foundEntry.open()) { foundEntry.open(true); } window.setTimeout(() => { if (path.length > 1) { foundEntry.highlightInside(path.slice(1)); } else { huePubSub.subscribeOnce('assist.db.scrollToComplete', () => { foundEntry.highlight(true); // Timeout is for animation effect window.setTimeout(() => { foundEntry.highlight(false); }, 1800); }); huePubSub.publish('assist.db.scrollTo', foundEntry); } }, 0); } }; if (self.entries().length === 0) { if (self.loading()) { const subscription = self.loading.subscribe(newVal => { if (!newVal) { subscription.dispose(); searchEntry(); } }); } else { self.loadEntries(searchEntry); } } else { searchEntry(); } } loadEntries(callback) { const self = this; if (!self.expandable || self.loading()) { return; } self.loading(true); const loadEntriesDeferred = $.Deferred(); const successCallback = sourceMeta => { self.entries([]); if (!sourceMeta.notFound) { self.catalogEntry .getChildren({ silenceErrors: self.navigationSettings.rightAssist }) .then(catalogEntries => { self.hasErrors(false); self.loading(false); self.loaded = true; if (catalogEntries.length === 0) { self.entries([]); return; } const newEntries = []; catalogEntries.forEach(catalogEntry => { newEntries.push(self.createEntry(catalogEntry)); }); if (sourceMeta.type === 'array') { self.entries(newEntries); self.entries()[0].open(true); } else { self.entries(newEntries); } loadEntriesDeferred.resolve(newEntries); if (typeof callback === 'function') { callback(); } }) .catch(() => { self.loading(false); self.loaded = true; self.hasErrors(true); }); if (!self.assistDbNamespace.nonSqlType) { self.catalogEntry .loadNavigatorMetaForChildren({ silenceErrors: self.navigationSettings.rightAssist }) .finally(() => {}); } } else { self.hasErrors(true); self.loading(false); self.loaded = true; if (typeof callback === 'function') { callback(); } } }; const errorCallback = () => { self.hasErrors(true); self.loading(false); self.loaded = true; loadEntriesDeferred.resolve([]); }; if ( !self.navigationSettings.rightAssist && window.HAS_SQL_ANALYZER && (self.catalogEntry.isTable() || self.catalogEntry.isDatabase()) && !self.assistDbNamespace.nonSqlType ) { const sqlAnalyzer = sqlAnalyzerRepository.getSqlAnalyzer(self.catalogEntry.getConnector()); self.catalogEntry .loadSqlAnalyzerPopularityForChildren({ silenceErrors: true, sqlAnalyzer }) .then(() => { loadEntriesDeferred.done(() => { if (!self.hasErrors()) { self.entries().forEach(entry => { if (entry.catalogEntry.sqlAnalyzerPopularity) { if (entry.catalogEntry.sqlAnalyzerPopularity.popularity) { entry.popularity(entry.catalogEntry.sqlAnalyzerPopularity.popularity); } else if (entry.catalogEntry.sqlAnalyzerPopularity.column_count) { entry.popularity(entry.catalogEntry.sqlAnalyzerPopularity.column_count); } else if (entry.catalogEntry.sqlAnalyzerPopularity.selectColumn) { entry.popularity( entry.catalogEntry.sqlAnalyzerPopularity.selectColumn.columnCount ); } } }); } }); }); } self.catalogEntry .getSourceMeta({ silenceErrors: self.navigationSettings.rightAssist }) .then(successCallback) .catch(errorCallback); } /** * @param {DataCatalogEntry} catalogEntry */ createEntry(catalogEntry) { const self = this; return new AssistDbEntry( catalogEntry, self, self.assistDbNamespace, self.filter, self.i18n, self.navigationSettings ); } getHierarchy() { const self = this; return self.catalogEntry.path.concat(); } async dblClick() { const self = this; if (self.catalogEntry.isTableOrView()) { huePubSub.publish('editor.insert.table.at.cursor', { name: await self.getTableName(), database: await self.getDatabaseName() }); } else if (self.catalogEntry.isColumn()) { huePubSub.publish('editor.insert.column.at.cursor', { name: await self.getColumnName(), table: await self.getTableName(), database: await self.getDatabaseName() }); } else { huePubSub.publish('editor.insert.column.at.cursor', { name: await self.getComplexName(), table: await self.getTableName(), database: await self.getDatabaseName() }); } } async explore(isSolr) { const self = this; if (isSolr) { huePubSub.publish('open.link', '/hue/dashboard/browse/' + self.catalogEntry.name); } else { huePubSub.publish( 'open.link', '/hue/dashboard/browse/' + (await self.getDatabaseName()) + '.' + (await self.getTableName()) + '?engine=' + self.assistDbNamespace.sourceType ); } } openInMetastore() { const self = this; let url; if (self.catalogEntry.isDatabase()) { url = '/metastore/tables/' + self.catalogEntry.name + '?connector_id=' + self.catalogEntry.getConnector().id + '&namespace=' + self.catalogEntry.namespace.id; } else if (self.catalogEntry.isTableOrView()) { url = '/metastore/table/' + self.parent.catalogEntry.name + '/' + self.catalogEntry.name + '?connector_id=' + self.catalogEntry.getConnector().id + '&namespace=' + self.catalogEntry.namespace.id; } else { return; } huePubSub.publish('open.link', url); } openInIndexer() { const self = this; const definitionName = self.catalogEntry.name; if (window.IS_NEW_INDEXER_ENABLED) { huePubSub.publish('open.link', '/indexer/indexes/' + definitionName); } else { const hash = '#edit/' + definitionName; if ( window.location.pathname.startsWith('/hue/indexer') && !window.location.pathname.startsWith('/hue/indexer/importer') ) { window.location.hash = hash; } else { huePubSub.subscribeOnce('app.gained.focus', app => { if (app === 'indexes') { window.setTimeout(() => { window.location.hash = hash; }, 0); } }); huePubSub.publish('open.link', '/indexer'); } } } toggleOpen() { const self = this; self.open(!self.open()); } openItem() { const self = this; if (self.catalogEntry.isTableOrView()) { huePubSub.publish('assist.table.selected', self.catalogEntry); } else if (self.catalogEntry.isDatabase()) { huePubSub.publish('assist.database.selected', self.catalogEntry); } } } export default AssistDbEntry;