gethue
Version:
Hue is an Open source SQL Query Editor for Databases/Warehouses
377 lines (339 loc) • 12 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 { ASSIST_ACTIVE_DB_CHANGED_EVENT } from './events';
import dataCatalog from '../../../catalog/dataCatalog';
import AssistDbEntry from '../../../ko/components/assist/assistDbEntry';
import huePubSub from '../../../utils/huePubSub';
import { getFromLocalStorage, setInLocalStorage } from '../../../utils/storageUtils';
class AssistDbNamespace {
/**
* @param {Object} options
* @param {Object} options.i18n
* @param {string} options.sourceType
* @param {Connector} options.connector
* @param {ContextNamespace} options.namespace
* @param {boolean} options.nonSqlType - Optional, default false
* @param {Object} options.navigationSettings
* @constructor
*/
constructor(options) {
const self = this;
self.i18n = options.i18n;
self.navigationSettings = options.navigationSettings;
self.sourceType = options.sourceType;
self.connector = options.connector;
self.nonSqlType = options.nonSqlType;
self.namespace = options.namespace;
self.status = ko.observable(options.namespace.status);
// TODO: Compute selection in assist?
self.compute = ko.observable();
if (self.namespace.computes.length) {
self.compute(self.namespace.computes[0]);
}
self.name = options.namespace.name;
self.dbIndex = {};
self.databases = ko.observableArray();
self.selectedDatabase = ko.observable();
self.highlight = ko.observable(false);
self.loadedPromise = $.Deferred();
self.loadedDeferred = $.Deferred();
self.loaded = ko.observable(false);
self.loading = ko.observable(false);
self.reloading = ko.observable(false);
self.hasErrors = ko.observable(false);
self.loadingTables = ko.pureComputed(
() =>
typeof self.selectedDatabase() !== 'undefined' &&
self.selectedDatabase() !== null &&
self.selectedDatabase().loading()
);
self.filter = {
querySpec: ko.observable({}).extend({ rateLimit: 300 })
};
self.hasEntries = ko.pureComputed(() => self.databases().length > 0);
self.filteredEntries = ko.pureComputed(() => {
if (
!self.filter.querySpec() ||
typeof self.filter.querySpec().query === 'undefined' ||
!self.filter.querySpec().query
) {
return self.databases();
}
return self
.databases()
.filter(
database =>
database.catalogEntry.name
.toLowerCase()
.indexOf(self.filter.querySpec().query.toLowerCase()) !== -1
);
});
self.autocompleteFromEntries = (nonPartial, partial) => {
const result = [];
const partialLower = partial.toLowerCase();
self.databases().forEach(db => {
if (db.catalogEntry.name.toLowerCase().indexOf(partialLower) === 0) {
result.push(nonPartial + partial + db.catalogEntry.name.substring(partial.length));
}
});
return result;
};
self.selectedDatabase.subscribe(() => {
const db = self.selectedDatabase();
if (db) {
huePubSub.publish(ASSIST_ACTIVE_DB_CHANGED_EVENT, {
connector: this.connector,
namespace: this.namespace,
database: db.name
});
}
if (window.HAS_SQL_ANALYZER && db && !db.popularityIndexSet && !self.nonSqlType) {
const sqlAnalyzer = sqlAnalyzerRepository.getSqlAnalyzer(db.catalogEntry.getConnector());
db.catalogEntry
.loadSqlAnalyzerPopularityForChildren({ silenceErrors: true, sqlAnalyzer })
.then(() => {
const applyPopularity = () => {
db.entries().forEach(entry => {
if (
entry.catalogEntry.sqlAnalyzerPopularity &&
entry.catalogEntry.sqlAnalyzerPopularity.popularity >= 5
) {
entry.popularity(entry.catalogEntry.sqlAnalyzerPopularity.popularity);
}
});
};
if (db.loading()) {
const subscription = db.loading.subscribe(() => {
subscription.dispose();
applyPopularity();
});
} else if (db.entries().length === 0) {
const subscription = db.entries.subscribe(newEntries => {
if (newEntries.length > 0) {
subscription.dispose();
applyPopularity();
}
});
} else {
applyPopularity();
}
});
}
});
self.selectedDatabaseChanged = () => {
if (self.selectedDatabase()) {
if (!self.selectedDatabase().hasEntries() && !self.selectedDatabase().loading()) {
self.selectedDatabase().loadEntries();
}
if (!self.navigationSettings.rightAssist) {
const entry = self.selectedDatabase().catalogEntry;
setInLocalStorage(
'assist_' + entry.getConnector().id + '_' + entry.namespace.id + '.lastSelectedDb',
entry.name
);
huePubSub.publish('assist.database.set', entry);
}
} else {
setInLocalStorage(
'assist_' + self.connector.id + '_' + self.namespace.id + '.lastSelectedDb',
null
);
}
};
const nestedFilter = {
querySpec: ko.observable({}).extend({ rateLimit: 300 })
};
self.setDatabase = databaseName => {
if (
databaseName &&
self.selectedDatabase() &&
databaseName === self.selectedDatabase().catalogEntry.name
) {
return;
}
if (databaseName && self.dbIndex[databaseName]) {
self.selectedDatabase(self.dbIndex[databaseName]);
self.selectedDatabaseChanged();
return;
}
let lastSelectedDb = getFromLocalStorage(
'assist_' + self.sourceType + '_' + self.namespace.id + '.lastSelectedDb'
);
if (!lastSelectedDb && lastSelectedDb !== '') {
lastSelectedDb = 'default';
}
if (lastSelectedDb && self.dbIndex[lastSelectedDb]) {
self.selectedDatabase(self.dbIndex[lastSelectedDb]);
self.selectedDatabaseChanged();
} else if (lastSelectedDb && self.databases().length > 0) {
self.selectedDatabase(self.databases()[0]);
self.selectedDatabaseChanged();
}
};
self.initDatabases = callback => {
if (self.loading()) {
return;
}
self.loading(true);
self.hasErrors(false);
const lastSelectedDbName = self.selectedDatabase()
? self.selectedDatabase().catalogEntry.name
: null;
self.selectedDatabase(null);
self.databases([]);
dataCatalog
.getEntry({
namespace: self.namespace,
compute: self.compute(),
connector: self.connector,
path: [],
definition: { type: 'source' }
})
.then(catalogEntry => {
self.catalogEntry = catalogEntry;
self.catalogEntry
.getChildren({ silenceErrors: self.navigationSettings.rightAssist })
.then(databaseEntries => {
self.dbIndex = {};
let hasNavMeta = false;
const dbs = [];
databaseEntries.forEach(catalogEntry => {
hasNavMeta = hasNavMeta || !!catalogEntry.navigatorMeta;
const database = new AssistDbEntry(
catalogEntry,
null,
self,
nestedFilter,
self.i18n,
self.navigationSettings
);
self.dbIndex[catalogEntry.name] = database;
if (catalogEntry.name === lastSelectedDbName) {
self.selectedDatabase(database);
self.selectedDatabaseChanged();
}
dbs.push(database);
});
if (!hasNavMeta && !self.nonSqlType) {
try {
self.catalogEntry.loadNavigatorMetaForChildren({ silenceErrors: true });
} catch (err) {}
}
self.databases(dbs);
if (typeof callback === 'function') {
callback();
}
})
.catch(() => {
self.hasErrors(true);
})
.finally(() => {
self.loaded(true);
self.loadedDeferred.resolve();
self.loading(false);
self.reloading(false);
});
});
};
self.modalItem = ko.observable();
if (!self.navigationSettings.rightAssist) {
huePubSub.subscribe('data.catalog.entry.refreshed', details => {
if (
self.namespace.id !== details.entry.namespace.id ||
details.entry.getConnector().id !== self.sourceType
) {
return;
}
if (self.catalogEntry === details.entry) {
self.initDatabases();
} else {
const targetPath = details.entry.path.join('.');
const findAndReloadInside = entries => {
return entries.some(entry => {
if (entry.catalogEntry.path.join('.') === targetPath) {
entry.catalogEntry = details.entry;
entry.loadEntries();
return true;
}
return findAndReloadInside(entry.entries());
});
};
findAndReloadInside(self.databases());
}
});
}
}
whenLoaded(callback) {
const self = this;
self.loadedDeferred.done(callback);
}
highlightInside(catalogEntry) {
const self = this;
let foundDb;
const findDatabase = () => {
self.databases().forEach(db => {
db.highlight(false);
if (db.databaseName === catalogEntry.path[0]) {
foundDb = db;
}
});
if (foundDb) {
const whenLoaded = () => {
if (self.selectedDatabase() !== foundDb) {
self.selectedDatabase(foundDb);
}
if (!foundDb.open()) {
foundDb.open(true);
}
window.setTimeout(() => {
huePubSub.subscribeOnce('assist.db.scrollToComplete', () => {
foundDb.highlight(true);
// Timeout is for animation effect
window.setTimeout(() => {
foundDb.highlight(false);
}, 1800);
});
if (catalogEntry.path.length > 1) {
foundDb.highlightInside(catalogEntry.path.slice(1));
} else {
huePubSub.publish('assist.db.scrollTo', foundDb);
}
}, 0);
};
if (foundDb.hasEntries()) {
whenLoaded();
} else {
foundDb.loadEntries(whenLoaded);
}
}
};
if (!self.loaded()) {
self.initDatabases(findDatabase);
} else {
findDatabase();
}
}
triggerRefresh() {
const self = this;
if (self.catalogEntry) {
self.catalogEntry.clearCache();
}
}
}
export default AssistDbNamespace;