gethue
Version:
Hue is an Open source SQL Query Editor for Databases/Warehouses
571 lines (517 loc) • 17.6 kB
JavaScript
// 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;