mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-26 01:25:38 -05:00
No browser from common (#7178)
* no browser from common * clean up some imports
This commit is contained in:
170
src/sql/workbench/services/notebook/browser/notebookService.ts
Normal file
170
src/sql/workbench/services/notebook/browser/notebookService.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { RenderMimeRegistry } from 'sql/workbench/parts/notebook/browser/outputs/registry';
|
||||
import { ModelFactory } from 'sql/workbench/parts/notebook/browser/models/modelFactory';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
import { NotebookInput } from 'sql/workbench/parts/notebook/browser/models/notebookInput';
|
||||
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { ICellModel, INotebookModel } from 'sql/workbench/parts/notebook/browser/models/modelInterfaces';
|
||||
import { NotebookChangeType } from 'sql/workbench/parts/notebook/common/models/contracts';
|
||||
import { IBootstrapParams } from 'sql/platform/bootstrap/common/bootstrapParams';
|
||||
|
||||
export const SERVICE_ID = 'notebookService';
|
||||
export const INotebookService = createDecorator<INotebookService>(SERVICE_ID);
|
||||
|
||||
export const DEFAULT_NOTEBOOK_PROVIDER = 'builtin';
|
||||
export const DEFAULT_NOTEBOOK_FILETYPE = 'IPYNB';
|
||||
export const SQL_NOTEBOOK_PROVIDER = 'sql';
|
||||
export const OVERRIDE_EDITOR_THEMING_SETTING = 'notebook.overrideEditorTheming';
|
||||
|
||||
export interface ILanguageMagic {
|
||||
magic: string;
|
||||
language: string;
|
||||
kernels?: string[];
|
||||
executionTarget?: string;
|
||||
}
|
||||
|
||||
export interface INotebookService {
|
||||
_serviceBrand: any;
|
||||
|
||||
readonly onNotebookEditorAdd: Event<INotebookEditor>;
|
||||
readonly onNotebookEditorRemove: Event<INotebookEditor>;
|
||||
onNotebookEditorRename: Event<INotebookEditor>;
|
||||
|
||||
readonly isRegistrationComplete: boolean;
|
||||
readonly registrationComplete: Promise<void>;
|
||||
readonly languageMagics: ILanguageMagic[];
|
||||
/**
|
||||
* Register a metadata provider
|
||||
*/
|
||||
registerProvider(providerId: string, provider: INotebookProvider): void;
|
||||
|
||||
/**
|
||||
* Register a metadata provider
|
||||
*/
|
||||
unregisterProvider(providerId: string): void;
|
||||
|
||||
registerNavigationProvider(provider: INavigationProvider): void;
|
||||
|
||||
getNavigationProvider(notebookUri: URI): INavigationProvider;
|
||||
|
||||
getSupportedFileExtensions(): string[];
|
||||
|
||||
getProvidersForFileType(fileType: string): string[];
|
||||
|
||||
getStandardKernelsForProvider(provider: string): azdata.nb.IStandardKernel[];
|
||||
|
||||
/**
|
||||
* Initializes and returns a Notebook manager that can handle all important calls to open, display, and
|
||||
* run cells in a notebook.
|
||||
* @param providerId ID for the provider to be used to instantiate a backend notebook service
|
||||
* @param uri URI for a notebook that is to be opened. Based on this an existing manager may be used, or
|
||||
* a new one may need to be created
|
||||
*/
|
||||
getOrCreateNotebookManager(providerId: string, uri: URI): Thenable<INotebookManager>;
|
||||
|
||||
addNotebookEditor(editor: INotebookEditor): void;
|
||||
|
||||
removeNotebookEditor(editor: INotebookEditor): void;
|
||||
|
||||
listNotebookEditors(): INotebookEditor[];
|
||||
|
||||
findNotebookEditor(notebookUri: URI): INotebookEditor | undefined;
|
||||
|
||||
getMimeRegistry(): RenderMimeRegistry;
|
||||
|
||||
renameNotebookEditor(oldUri: URI, newUri: URI, currentEditor: INotebookEditor): void;
|
||||
|
||||
/**
|
||||
* Checks if a notebook has previously been marked as trusted, and that
|
||||
* the notebook has not changed on disk since that time. If the notebook
|
||||
* is currently dirty in the app, the previous trusted state will be used even
|
||||
* if it's altered on disk since the version in our UI is based on previously trusted
|
||||
* content.
|
||||
* @param notebookUri the URI identifying a notebook
|
||||
* @param isDirty is the notebook marked as dirty in by the text model trackers?
|
||||
*/
|
||||
isNotebookTrustCached(notebookUri: URI, isDirty: boolean): Promise<boolean>;
|
||||
/**
|
||||
* Serializes an impactful Notebook state change. This will result
|
||||
* in trusted state being serialized if needed, and notifications being
|
||||
* sent to listeners that can act on the point-in-time notebook state
|
||||
* @param notebookUri the URI identifying a notebook
|
||||
*/
|
||||
serializeNotebookStateChange(notebookUri: URI, changeType: NotebookChangeType, cell?: ICellModel): void;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param notebookUri URI of the notebook to navigate to
|
||||
* @param sectionId ID of the section to navigate to
|
||||
*/
|
||||
navigateTo(notebookUri: URI, sectionId: string): void;
|
||||
}
|
||||
|
||||
export interface INotebookProvider {
|
||||
readonly providerId: string;
|
||||
getNotebookManager(notebookUri: URI): Thenable<INotebookManager>;
|
||||
handleNotebookClosed(notebookUri: URI): void;
|
||||
}
|
||||
|
||||
export interface INotebookManager {
|
||||
providerId: string;
|
||||
readonly contentManager: azdata.nb.ContentManager;
|
||||
readonly sessionManager: azdata.nb.SessionManager;
|
||||
readonly serverManager: azdata.nb.ServerManager;
|
||||
}
|
||||
|
||||
export interface IProviderInfo {
|
||||
providerId: string;
|
||||
providers: string[];
|
||||
}
|
||||
export interface INotebookParams extends IBootstrapParams {
|
||||
notebookUri: URI;
|
||||
input: NotebookInput;
|
||||
providerInfo: Promise<IProviderInfo>;
|
||||
profile?: IConnectionProfile;
|
||||
modelFactory?: ModelFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a section in a notebook as the header text for that section,
|
||||
* the relative URI that can be used to link to it inside Notebook documents
|
||||
*/
|
||||
export interface INotebookSection {
|
||||
header: string;
|
||||
relativeUri: string;
|
||||
}
|
||||
|
||||
export interface INotebookEditor {
|
||||
readonly notebookParams: INotebookParams;
|
||||
readonly id: string;
|
||||
readonly cells?: ICellModel[];
|
||||
readonly modelReady: Promise<INotebookModel>;
|
||||
readonly model: INotebookModel | null;
|
||||
isDirty(): boolean;
|
||||
isActive(): boolean;
|
||||
isVisible(): boolean;
|
||||
executeEdits(edits: ISingleNotebookEditOperation[]): boolean;
|
||||
runCell(cell: ICellModel): Promise<boolean>;
|
||||
runAllCells(startCell?: ICellModel, endCell?: ICellModel): Promise<boolean>;
|
||||
clearOutput(cell: ICellModel): Promise<boolean>;
|
||||
clearAllOutputs(): Promise<boolean>;
|
||||
getSections(): INotebookSection[];
|
||||
navigateToSection(sectionId: string): void;
|
||||
}
|
||||
|
||||
export interface INavigationProvider {
|
||||
providerId: string;
|
||||
hasNavigation: boolean;
|
||||
getNavigation(uri: URI): Thenable<azdata.nb.NavigationResult>;
|
||||
onNext(uri: URI): void;
|
||||
onPrevious(uri: URI): void;
|
||||
}
|
||||
@@ -0,0 +1,644 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { nb } from 'azdata';
|
||||
import { localize } from 'vs/nls';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
|
||||
import {
|
||||
INotebookService, INotebookManager, INotebookProvider,
|
||||
DEFAULT_NOTEBOOK_FILETYPE, INotebookEditor, SQL_NOTEBOOK_PROVIDER, OVERRIDE_EDITOR_THEMING_SETTING, INavigationProvider, ILanguageMagic
|
||||
} from 'sql/workbench/services/notebook/browser/notebookService';
|
||||
import { RenderMimeRegistry } from 'sql/workbench/parts/notebook/browser/outputs/registry';
|
||||
import { standardRendererFactories } from 'sql/workbench/parts/notebook/browser/outputs/factories';
|
||||
import { Extensions, INotebookProviderRegistry, NotebookProviderRegistration } from 'sql/workbench/services/notebook/common/notebookRegistry';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Memento } from 'vs/workbench/common/memento';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IExtensionManagementService, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Deferred } from 'sql/base/common/promise';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { NotebookEditorVisibleContext } from 'sql/workbench/services/notebook/common/notebookContext';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { NotebookEditor } from 'sql/workbench/parts/notebook/browser/notebookEditor';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { registerNotebookThemes } from 'sql/workbench/parts/notebook/browser/notebookStyles';
|
||||
import { IQueryManagementService } from 'sql/platform/query/common/queryManagement';
|
||||
import { notebookConstants, ICellModel } from 'sql/workbench/parts/notebook/browser/models/modelInterfaces';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { SqlNotebookProvider } from 'sql/workbench/services/notebook/browser/sql/sqlNotebookProvider';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { keys } from 'vs/base/common/map';
|
||||
import { IFileService, IFileStatWithMetadata } from 'vs/platform/files/common/files';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { NotebookChangeType } from 'sql/workbench/parts/notebook/common/models/contracts';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
|
||||
export interface NotebookProviderProperties {
|
||||
provider: string;
|
||||
fileExtensions: string[];
|
||||
}
|
||||
|
||||
interface NotebookProviderCache {
|
||||
[id: string]: NotebookProviderProperties;
|
||||
}
|
||||
|
||||
interface NotebookProvidersMemento {
|
||||
notebookProviderCache: NotebookProviderCache;
|
||||
}
|
||||
|
||||
interface TrustedNotebookMetadata {
|
||||
mtime: number;
|
||||
}
|
||||
interface TrustedNotebookCache {
|
||||
// URI goes to cached
|
||||
[uri: string]: TrustedNotebookMetadata;
|
||||
}
|
||||
|
||||
interface TrustedNotebooksMemento {
|
||||
trustedNotebooksCache: TrustedNotebookCache;
|
||||
}
|
||||
|
||||
const notebookRegistry = Registry.as<INotebookProviderRegistry>(Extensions.NotebookProviderContribution);
|
||||
|
||||
class ProviderDescriptor {
|
||||
private _instanceReady = new Deferred<INotebookProvider>();
|
||||
constructor(private providerId: string, private _instance?: INotebookProvider) {
|
||||
if (_instance) {
|
||||
this._instanceReady.resolve(_instance);
|
||||
}
|
||||
}
|
||||
|
||||
public get instanceReady(): Promise<INotebookProvider> {
|
||||
return this._instanceReady.promise;
|
||||
}
|
||||
|
||||
public get instance(): INotebookProvider {
|
||||
return this._instance;
|
||||
}
|
||||
public set instance(value: INotebookProvider) {
|
||||
this._instance = value;
|
||||
this._instanceReady.resolve(value);
|
||||
}
|
||||
}
|
||||
|
||||
export class NotebookService extends Disposable implements INotebookService {
|
||||
_serviceBrand: any;
|
||||
|
||||
private _providersMemento: Memento;
|
||||
private _trustedNotebooksMemento: Memento;
|
||||
private _mimeRegistry: RenderMimeRegistry;
|
||||
private _providers: Map<string, ProviderDescriptor> = new Map();
|
||||
private _navigationProviders: Map<string, INavigationProvider> = new Map();
|
||||
private _managersMap: Map<string, INotebookManager[]> = new Map();
|
||||
private _onNotebookEditorAdd = new Emitter<INotebookEditor>();
|
||||
private _onNotebookEditorRemove = new Emitter<INotebookEditor>();
|
||||
private _onCellChanged = new Emitter<INotebookEditor>();
|
||||
private _onNotebookEditorRename = new Emitter<INotebookEditor>();
|
||||
private _editors = new Map<string, INotebookEditor>();
|
||||
private _fileToProviders = new Map<string, NotebookProviderRegistration[]>();
|
||||
private _providerToStandardKernels = new Map<string, nb.IStandardKernel[]>();
|
||||
private _registrationComplete = new Deferred<void>();
|
||||
private _isRegistrationComplete = false;
|
||||
private notebookEditorVisible: IContextKey<boolean>;
|
||||
private _themeParticipant: IDisposable;
|
||||
private _overrideEditorThemeSetting: boolean;
|
||||
private _trustedCacheQueue: URI[] = [];
|
||||
private _updateTrustCacheScheduler: RunOnceScheduler;
|
||||
|
||||
constructor(
|
||||
@ILifecycleService lifecycleService: ILifecycleService,
|
||||
@IStorageService private _storageService: IStorageService,
|
||||
@IExtensionService private _extensionService: IExtensionService,
|
||||
@IExtensionManagementService extensionManagementService: IExtensionManagementService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IContextKeyService private _contextKeyService: IContextKeyService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@IQueryManagementService private readonly _queryManagementService: IQueryManagementService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService
|
||||
) {
|
||||
super();
|
||||
this._providersMemento = new Memento('notebookProviders', this._storageService);
|
||||
this._trustedNotebooksMemento = new Memento('notebooks.trusted', this._storageService);
|
||||
|
||||
this._updateTrustCacheScheduler = new RunOnceScheduler(() => this.updateTrustedCache(), 250);
|
||||
this._register(notebookRegistry.onNewRegistration(this.updateRegisteredProviders, this));
|
||||
this.registerBuiltInProvider();
|
||||
// If a provider has been already registered, the onNewRegistration event will not have a listener attached yet
|
||||
// So, explicitly updating registered providers here.
|
||||
if (notebookRegistry.providers.length > 0) {
|
||||
notebookRegistry.providers.forEach(p => {
|
||||
// Don't need to re-register SQL_NOTEBOOK_PROVIDER
|
||||
if (p.provider !== SQL_NOTEBOOK_PROVIDER) {
|
||||
this.updateRegisteredProviders({ id: p.provider, registration: p });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (this._extensionService) {
|
||||
this._extensionService.whenInstalledExtensionsRegistered().then(() => {
|
||||
this.cleanupProviders();
|
||||
|
||||
// If providers have already registered by this point, add them now (since onHandlerAdded will never fire)
|
||||
if (this._queryManagementService.getRegisteredProviders().length > 0) {
|
||||
this.updateSQLRegistrationWithConnectionProviders();
|
||||
}
|
||||
|
||||
this._register(this._queryManagementService.onHandlerAdded((queryType) => {
|
||||
this.updateSQLRegistrationWithConnectionProviders();
|
||||
}));
|
||||
});
|
||||
}
|
||||
if (extensionManagementService) {
|
||||
this._register(extensionManagementService.onDidUninstallExtension(({ identifier }) => this.removeContributedProvidersFromCache(identifier, this._extensionService)));
|
||||
}
|
||||
|
||||
lifecycleService.onWillShutdown(() => this.shutdown());
|
||||
this.hookContextKeyListeners();
|
||||
this.hookNotebookThemesAndConfigListener();
|
||||
// Temporary (issue #6427 will remove): Add a product quality key so we can only show books on Insiders
|
||||
this._contextKeyService.createKey<string>('notebookQuality', environmentService.appQuality);
|
||||
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
if (this._themeParticipant) {
|
||||
this._themeParticipant.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private hookContextKeyListeners(): void {
|
||||
const updateEditorContextKeys = () => {
|
||||
const visibleEditors = this._editorService.visibleControls;
|
||||
this.notebookEditorVisible.set(visibleEditors.some(control => control.getId() === NotebookEditor.ID));
|
||||
};
|
||||
if (this._contextKeyService) {
|
||||
this.notebookEditorVisible = NotebookEditorVisibleContext.bindTo(this._contextKeyService);
|
||||
}
|
||||
if (this._editorService) {
|
||||
this._register(this._editorService.onDidActiveEditorChange(() => updateEditorContextKeys()));
|
||||
this._register(this._editorService.onDidVisibleEditorsChange(() => updateEditorContextKeys()));
|
||||
this._register(this._editorGroupsService.onDidAddGroup(() => updateEditorContextKeys()));
|
||||
this._register(this._editorGroupsService.onDidRemoveGroup(() => updateEditorContextKeys()));
|
||||
}
|
||||
}
|
||||
|
||||
private hookNotebookThemesAndConfigListener(): void {
|
||||
if (this._configurationService) {
|
||||
this.updateNotebookThemes();
|
||||
this._register(this._configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(OVERRIDE_EDITOR_THEMING_SETTING)
|
||||
|| e.affectsConfiguration('resultsGrid')) {
|
||||
this.updateNotebookThemes();
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private updateSQLRegistrationWithConnectionProviders() {
|
||||
// Update the SQL extension
|
||||
let sqlNotebookProvider = this._providerToStandardKernels.get(notebookConstants.SQL);
|
||||
if (sqlNotebookProvider) {
|
||||
let sqlConnectionTypes = this._queryManagementService.getRegisteredProviders();
|
||||
let provider = sqlNotebookProvider.find(p => p.name === notebookConstants.SQL);
|
||||
if (provider) {
|
||||
this._providerToStandardKernels.set(notebookConstants.SQL, [{
|
||||
name: notebookConstants.SQL,
|
||||
displayName: notebookConstants.SQL,
|
||||
connectionProviderIds: sqlConnectionTypes
|
||||
}]);
|
||||
}
|
||||
}
|
||||
this._isRegistrationComplete = true;
|
||||
this._registrationComplete.resolve();
|
||||
}
|
||||
|
||||
private updateNotebookThemes() {
|
||||
let overrideEditorSetting = this._configurationService.getValue<boolean>(OVERRIDE_EDITOR_THEMING_SETTING);
|
||||
if (overrideEditorSetting !== this._overrideEditorThemeSetting) {
|
||||
// Re-add the participant since this will trigger update of theming rules, can't just
|
||||
// update something and ask to change
|
||||
if (this._themeParticipant) {
|
||||
this._themeParticipant.dispose();
|
||||
}
|
||||
this._overrideEditorThemeSetting = overrideEditorSetting;
|
||||
this._themeParticipant = registerNotebookThemes(overrideEditorSetting, this._configurationService);
|
||||
}
|
||||
}
|
||||
|
||||
private updateRegisteredProviders(p: { id: string; registration: NotebookProviderRegistration; }) {
|
||||
let registration = p.registration;
|
||||
|
||||
if (!this._providers.has(p.id)) {
|
||||
this._providers.set(p.id, new ProviderDescriptor(p.id));
|
||||
}
|
||||
if (registration.fileExtensions) {
|
||||
if (Array.isArray<string>(registration.fileExtensions)) {
|
||||
for (let fileType of registration.fileExtensions) {
|
||||
this.addFileProvider(fileType, registration);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.addFileProvider(registration.fileExtensions, registration);
|
||||
}
|
||||
}
|
||||
if (registration.standardKernels) {
|
||||
this.addStandardKernels(registration);
|
||||
}
|
||||
}
|
||||
|
||||
registerProvider(providerId: string, instance: INotebookProvider): void {
|
||||
let providerDescriptor = this._providers.get(providerId);
|
||||
if (providerDescriptor) {
|
||||
// Update, which will resolve the promise for anyone waiting on the instance to be registered
|
||||
providerDescriptor.instance = instance;
|
||||
} else {
|
||||
this._providers.set(providerId, new ProviderDescriptor(providerId, instance));
|
||||
}
|
||||
}
|
||||
|
||||
unregisterProvider(providerId: string): void {
|
||||
this._providers.delete(providerId);
|
||||
}
|
||||
|
||||
registerNavigationProvider(provider: INavigationProvider): void {
|
||||
this._navigationProviders.set(provider.providerId, provider);
|
||||
}
|
||||
|
||||
getNavigationProvider(): INavigationProvider {
|
||||
let provider = this._navigationProviders.size > 0 ? this._navigationProviders.values().next().value : undefined;
|
||||
return provider;
|
||||
}
|
||||
|
||||
get isRegistrationComplete(): boolean {
|
||||
return this._isRegistrationComplete;
|
||||
}
|
||||
|
||||
get registrationComplete(): Promise<void> {
|
||||
return this._registrationComplete.promise;
|
||||
}
|
||||
|
||||
private addFileProvider(fileType: string, provider: NotebookProviderRegistration) {
|
||||
let providers = this._fileToProviders.get(fileType.toUpperCase());
|
||||
if (!providers) {
|
||||
providers = [];
|
||||
}
|
||||
providers.push(provider);
|
||||
this._fileToProviders.set(fileType.toUpperCase(), providers);
|
||||
}
|
||||
|
||||
// Standard kernels are contributed where a list of kernels are defined that can be shown
|
||||
// in the kernels dropdown list before a SessionManager has been started; this way,
|
||||
// every NotebookProvider doesn't need to have an active SessionManager in order to contribute
|
||||
// kernels to the dropdown
|
||||
private addStandardKernels(provider: NotebookProviderRegistration) {
|
||||
let providerUpperCase = provider.provider.toUpperCase();
|
||||
let standardKernels = this._providerToStandardKernels.get(providerUpperCase);
|
||||
if (!standardKernels) {
|
||||
standardKernels = [];
|
||||
}
|
||||
if (Array.isArray(provider.standardKernels)) {
|
||||
provider.standardKernels.forEach(kernel => {
|
||||
standardKernels.push(kernel);
|
||||
});
|
||||
} else {
|
||||
standardKernels.push(provider.standardKernels);
|
||||
}
|
||||
this._providerToStandardKernels.set(providerUpperCase, standardKernels);
|
||||
}
|
||||
|
||||
getSupportedFileExtensions(): string[] {
|
||||
return Array.from(keys(this._fileToProviders));
|
||||
}
|
||||
|
||||
getProvidersForFileType(fileType: string): string[] {
|
||||
fileType = fileType.toUpperCase();
|
||||
let providers = this._fileToProviders.get(fileType);
|
||||
|
||||
return providers ? providers.map(provider => provider.provider) : undefined;
|
||||
}
|
||||
|
||||
getStandardKernelsForProvider(provider: string): nb.IStandardKernel[] {
|
||||
return this._providerToStandardKernels.get(provider.toUpperCase());
|
||||
}
|
||||
|
||||
private shutdown(): void {
|
||||
this._managersMap.forEach(manager => {
|
||||
manager.forEach(m => {
|
||||
if (m.serverManager) {
|
||||
// TODO should this thenable be awaited?
|
||||
m.serverManager.stopServer();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async getOrCreateNotebookManager(providerId: string, uri: URI): Promise<INotebookManager> {
|
||||
if (!uri) {
|
||||
throw new Error(localize('notebookUriNotDefined', "No URI was passed when creating a notebook manager"));
|
||||
}
|
||||
let uriString = uri.toString();
|
||||
let managers: INotebookManager[] = this._managersMap.get(uriString);
|
||||
// If manager already exists for a given notebook, return it
|
||||
if (managers) {
|
||||
let index = managers.findIndex(m => m.providerId === providerId);
|
||||
if (index && index >= 0) {
|
||||
return managers[index];
|
||||
}
|
||||
}
|
||||
let newManager = await this.doWithProvider(providerId, (provider) => provider.getNotebookManager(uri));
|
||||
|
||||
managers = managers || [];
|
||||
managers.push(newManager);
|
||||
this._managersMap.set(uriString, managers);
|
||||
return newManager;
|
||||
}
|
||||
|
||||
get onNotebookEditorAdd(): Event<INotebookEditor> {
|
||||
return this._onNotebookEditorAdd.event;
|
||||
}
|
||||
get onNotebookEditorRemove(): Event<INotebookEditor> {
|
||||
return this._onNotebookEditorRemove.event;
|
||||
}
|
||||
get onCellChanged(): Event<INotebookEditor> {
|
||||
return this._onCellChanged.event;
|
||||
}
|
||||
|
||||
get onNotebookEditorRename(): Event<INotebookEditor> {
|
||||
return this._onNotebookEditorRename.event;
|
||||
}
|
||||
|
||||
addNotebookEditor(editor: INotebookEditor): void {
|
||||
this._editors.set(editor.id, editor);
|
||||
this._onNotebookEditorAdd.fire(editor);
|
||||
}
|
||||
|
||||
removeNotebookEditor(editor: INotebookEditor): void {
|
||||
if (this._editors.delete(editor.id)) {
|
||||
this._onNotebookEditorRemove.fire(editor);
|
||||
}
|
||||
// Remove the manager from the tracked list, and let the notebook provider know that it should update its mappings
|
||||
this.sendNotebookCloseToProvider(editor);
|
||||
}
|
||||
|
||||
listNotebookEditors(): INotebookEditor[] {
|
||||
let editors = [];
|
||||
this._editors.forEach(e => editors.push(e));
|
||||
return editors;
|
||||
}
|
||||
|
||||
findNotebookEditor(notebookUri: URI): INotebookEditor | undefined {
|
||||
if (!notebookUri) {
|
||||
return undefined;
|
||||
}
|
||||
let uriString = notebookUri.toString();
|
||||
let editor = this.listNotebookEditors().find(n => n.id === uriString);
|
||||
return editor;
|
||||
}
|
||||
|
||||
renameNotebookEditor(oldUri: URI, newUri: URI, currentEditor: INotebookEditor): void {
|
||||
let oldUriKey = oldUri.toString();
|
||||
if (this._editors.has(oldUriKey)) {
|
||||
this._editors.delete(oldUriKey);
|
||||
currentEditor.notebookParams.notebookUri = newUri;
|
||||
this._editors.set(newUri.toString(), currentEditor);
|
||||
this._onNotebookEditorRename.fire(currentEditor);
|
||||
}
|
||||
}
|
||||
|
||||
get languageMagics(): ILanguageMagic[] {
|
||||
return notebookRegistry.languageMagics;
|
||||
}
|
||||
|
||||
// PRIVATE HELPERS /////////////////////////////////////////////////////
|
||||
|
||||
private sendNotebookCloseToProvider(editor: INotebookEditor): void {
|
||||
let notebookUri = editor.notebookParams.notebookUri;
|
||||
let uriString = notebookUri.toString();
|
||||
let manager = this._managersMap.get(uriString);
|
||||
if (manager) {
|
||||
// As we have a manager, we can assume provider is ready
|
||||
this._managersMap.delete(uriString);
|
||||
manager.forEach(m => {
|
||||
let provider = this._providers.get(m.providerId);
|
||||
provider.instance.handleNotebookClosed(notebookUri);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async doWithProvider<T>(providerId: string, op: (provider: INotebookProvider) => Thenable<T>): Promise<T> {
|
||||
// Make sure the provider exists before attempting to retrieve accounts
|
||||
let provider: INotebookProvider = await this.getProviderInstance(providerId);
|
||||
return op(provider);
|
||||
}
|
||||
|
||||
private async getProviderInstance(providerId: string, timeout?: number): Promise<INotebookProvider> {
|
||||
let providerDescriptor = this._providers.get(providerId);
|
||||
let instance: INotebookProvider;
|
||||
|
||||
// Try get from actual provider, waiting on its registration
|
||||
if (providerDescriptor) {
|
||||
if (!providerDescriptor.instance) {
|
||||
// Await extension registration before awaiting provider registration
|
||||
try {
|
||||
await this._extensionService.whenInstalledExtensionsRegistered;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
instance = await this.waitOnProviderAvailability(providerDescriptor);
|
||||
} else {
|
||||
instance = providerDescriptor.instance;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to default (SQL) if this failed
|
||||
if (!instance) {
|
||||
providerDescriptor = this._providers.get(SQL_NOTEBOOK_PROVIDER);
|
||||
instance = providerDescriptor ? providerDescriptor.instance : undefined;
|
||||
}
|
||||
|
||||
// Should never happen, but if default wasn't registered we should throw
|
||||
if (!instance) {
|
||||
throw new Error(localize('notebookServiceNoProvider', "Notebook provider does not exist"));
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
private waitOnProviderAvailability(providerDescriptor: ProviderDescriptor, timeout?: number): Promise<INotebookProvider> {
|
||||
// Wait up to 30 seconds for the provider to be registered
|
||||
timeout = timeout || 30000;
|
||||
let promises: Promise<INotebookProvider>[] = [
|
||||
providerDescriptor.instanceReady,
|
||||
new Promise<INotebookProvider>((resolve, reject) => setTimeout(() => resolve(), timeout))
|
||||
];
|
||||
return Promise.race(promises);
|
||||
}
|
||||
|
||||
//Returns an instantiation of RenderMimeRegistry class
|
||||
getMimeRegistry(): RenderMimeRegistry {
|
||||
if (!this._mimeRegistry) {
|
||||
return new RenderMimeRegistry({
|
||||
initialFactories: standardRendererFactories
|
||||
});
|
||||
}
|
||||
return this._mimeRegistry;
|
||||
}
|
||||
|
||||
private get providersMemento(): NotebookProvidersMemento {
|
||||
return this._providersMemento.getMemento(StorageScope.GLOBAL) as NotebookProvidersMemento;
|
||||
}
|
||||
|
||||
private get trustedNotebooksMemento(): TrustedNotebooksMemento {
|
||||
let cache = this._trustedNotebooksMemento.getMemento(StorageScope.GLOBAL) as TrustedNotebooksMemento;
|
||||
if (!cache.trustedNotebooksCache) {
|
||||
cache.trustedNotebooksCache = {};
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
private cleanupProviders(): void {
|
||||
let knownProviders = Object.keys(notebookRegistry.providers);
|
||||
let cache = this.providersMemento.notebookProviderCache;
|
||||
for (let key in cache) {
|
||||
if (!knownProviders.includes(key)) {
|
||||
this._providers.delete(key);
|
||||
delete cache[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private registerBuiltInProvider() {
|
||||
let sqlProvider = new SqlNotebookProvider(this._instantiationService);
|
||||
this.registerProvider(sqlProvider.providerId, sqlProvider);
|
||||
notebookRegistry.registerNotebookProvider({
|
||||
provider: sqlProvider.providerId,
|
||||
fileExtensions: DEFAULT_NOTEBOOK_FILETYPE,
|
||||
standardKernels: { name: notebookConstants.SQL, displayName: notebookConstants.SQL, connectionProviderIds: [notebookConstants.SQL_CONNECTION_PROVIDER] }
|
||||
});
|
||||
}
|
||||
|
||||
private removeContributedProvidersFromCache(identifier: IExtensionIdentifier, extensionService: IExtensionService) {
|
||||
const notebookProvider = 'notebookProvider';
|
||||
extensionService.getExtensions().then(i => {
|
||||
let extension = i.find(c => c.identifier.value.toLowerCase() === identifier.id.toLowerCase());
|
||||
if (extension && extension.contributes
|
||||
&& extension.contributes[notebookProvider]
|
||||
&& extension.contributes[notebookProvider].providerId) {
|
||||
let id = extension.contributes[notebookProvider].providerId;
|
||||
delete this.providersMemento.notebookProviderCache[id];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async isNotebookTrustCached(notebookUri: URI, isDirty: boolean): Promise<boolean> {
|
||||
if (notebookUri.scheme === Schemas.untitled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let cacheInfo = this.trustedNotebooksMemento.trustedNotebooksCache[notebookUri.toString()];
|
||||
if (!cacheInfo) {
|
||||
// This notebook was never trusted
|
||||
return false;
|
||||
}
|
||||
// This was trusted. If it's not dirty (e.g. if we're not working on our cached copy)
|
||||
// then should verify it's not been modified on disk since that invalidates trust relationship
|
||||
if (!isDirty) {
|
||||
// Check mtime against mtime on disk
|
||||
let actualMtime: number = await this.getModifiedTimeForFile(notebookUri);
|
||||
if (actualMtime > cacheInfo.mtime) {
|
||||
// Modified since last use, so can't guarantee trust.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private async getModifiedTimeForFile(notebookUri: URI): Promise<number> {
|
||||
try {
|
||||
let fstat: IFileStatWithMetadata = await this._fileService.resolve(notebookUri, {
|
||||
resolveMetadata: true
|
||||
});
|
||||
return fstat ? fstat.mtime : 0;
|
||||
} catch (err) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
serializeNotebookStateChange(notebookUri: URI, changeType: NotebookChangeType, cell?: ICellModel): void {
|
||||
if (notebookUri.scheme !== Schemas.untitled) {
|
||||
// Conditions for saving:
|
||||
// 1. Not untitled. They're always trusted as we open them
|
||||
// 2. Serialization action was a save, since don't need to update on execution etc.
|
||||
// 3. Not already saving (e.g. isn't in the queue to be cached)
|
||||
// 4. Notebook is trusted. Don't need to save state of untrusted notebooks
|
||||
let notebookUriString = notebookUri.toString();
|
||||
if (changeType === NotebookChangeType.Saved && this._trustedCacheQueue.findIndex(uri => uri.toString() === notebookUriString) < 0) {
|
||||
// Only save if it's trusted
|
||||
let notebook = this.listNotebookEditors().find(n => n.id === notebookUriString);
|
||||
if (notebook && notebook.model.trustedMode) {
|
||||
this._trustedCacheQueue.push(notebookUri);
|
||||
this._updateTrustCacheScheduler.schedule();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let editor = this.findNotebookEditor(notebookUri);
|
||||
if (editor && editor.model) {
|
||||
editor.model.serializationStateChanged(changeType, cell);
|
||||
// TODO add history notification if a non-untitled notebook has a state change
|
||||
}
|
||||
}
|
||||
|
||||
private async updateTrustedCache(): Promise<void> {
|
||||
try {
|
||||
if (this._trustedCacheQueue.length > 0) {
|
||||
// Copy out all items from the cache
|
||||
let items = this._trustedCacheQueue;
|
||||
this._trustedCacheQueue = [];
|
||||
|
||||
// Get all the file stats and then serialize this to a memento
|
||||
let itemConfig = items.map(item => {
|
||||
return { resource: item, options: { resolveMetadata: true } };
|
||||
});
|
||||
let metadata = await this._fileService.resolveAll(itemConfig);
|
||||
let trustedCache = this.trustedNotebooksMemento.trustedNotebooksCache;
|
||||
for (let i = 0; i < metadata.length; i++) {
|
||||
let item = items[i];
|
||||
let stat = metadata[i] && metadata[i].stat;
|
||||
if (stat && stat.mtime) {
|
||||
trustedCache[item.toString()] = {
|
||||
mtime: stat.mtime
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
this._trustedNotebooksMemento.saveMemento();
|
||||
}
|
||||
} catch (err) {
|
||||
if (this._logService) {
|
||||
this._logService.trace(`Failed to save trust state to cache: ${toErrorMessage(err)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
navigateTo(notebookUri: URI, sectionId: string): void {
|
||||
let editor = this._editors.get(notebookUri.toString());
|
||||
if (editor) {
|
||||
editor.navigateToSection(sectionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
229
src/sql/workbench/services/notebook/browser/sessionManager.ts
Normal file
229
src/sql/workbench/services/notebook/browser/sessionManager.ts
Normal file
@@ -0,0 +1,229 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { nb } from 'azdata';
|
||||
import { localize } from 'vs/nls';
|
||||
import { FutureInternal } from 'sql/workbench/parts/notebook/browser/models/modelInterfaces';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
|
||||
export const noKernel: string = localize('noKernel', "No Kernel");
|
||||
const runNotebookDisabled = localize('runNotebookDisabled', "Cannot run cells as no kernel has been configured");
|
||||
|
||||
let noKernelSpec: nb.IKernelSpec = ({
|
||||
name: noKernel,
|
||||
language: 'python',
|
||||
display_name: noKernel
|
||||
});
|
||||
|
||||
export class SessionManager implements nb.SessionManager {
|
||||
public get isReady(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public get ready(): Thenable<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public get specs(): nb.IAllKernels {
|
||||
let allKernels: nb.IAllKernels = {
|
||||
defaultKernel: noKernel,
|
||||
kernels: [noKernelSpec]
|
||||
};
|
||||
return allKernels;
|
||||
}
|
||||
|
||||
startNew(options: nb.ISessionOptions): Thenable<nb.ISession> {
|
||||
let session = new EmptySession(options);
|
||||
return Promise.resolve(session);
|
||||
}
|
||||
|
||||
shutdown(id: string): Thenable<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
export class EmptySession implements nb.ISession {
|
||||
private _kernel: EmptyKernel;
|
||||
private _defaultKernelLoaded = false;
|
||||
|
||||
public set defaultKernelLoaded(value) {
|
||||
this._defaultKernelLoaded = value;
|
||||
}
|
||||
|
||||
public get defaultKernelLoaded(): boolean {
|
||||
return this._defaultKernelLoaded;
|
||||
}
|
||||
|
||||
constructor(private options: nb.ISessionOptions) {
|
||||
this._kernel = new EmptyKernel();
|
||||
}
|
||||
|
||||
public get canChangeKernels(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this.options.kernelId || '';
|
||||
}
|
||||
|
||||
public get path(): string {
|
||||
return this.options.path;
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
return this.options.name || '';
|
||||
}
|
||||
|
||||
public get type(): string {
|
||||
return this.options.type || '';
|
||||
}
|
||||
|
||||
public get status(): nb.KernelStatus {
|
||||
return 'connected';
|
||||
}
|
||||
|
||||
public get kernel(): nb.IKernel {
|
||||
return this._kernel;
|
||||
}
|
||||
|
||||
changeKernel(kernelInfo: nb.IKernelSpec): Thenable<nb.IKernel> {
|
||||
return Promise.resolve(this.kernel);
|
||||
}
|
||||
|
||||
// No kernel config necessary for empty session
|
||||
configureKernel(kernelInfo: nb.IKernelSpec): Thenable<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
configureConnection(connection: ConnectionProfile): Thenable<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
class EmptyKernel implements nb.IKernel {
|
||||
public get id(): string {
|
||||
return '-1';
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
return noKernel;
|
||||
}
|
||||
|
||||
public get supportsIntellisense(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public get requiresConnection(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public get isReady(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public get ready(): Thenable<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public get info(): nb.IInfoReply {
|
||||
let info: nb.IInfoReply = {
|
||||
protocol_version: '',
|
||||
implementation: '',
|
||||
implementation_version: '',
|
||||
language_info: {
|
||||
name: '',
|
||||
version: '',
|
||||
},
|
||||
banner: '',
|
||||
help_links: [{
|
||||
text: '',
|
||||
url: ''
|
||||
}]
|
||||
};
|
||||
|
||||
return info;
|
||||
}
|
||||
getSpec(): Thenable<nb.IKernelSpec> {
|
||||
return Promise.resolve(noKernelSpec);
|
||||
}
|
||||
|
||||
requestExecute(content: nb.IExecuteRequest, disposeOnDone?: boolean): nb.IFuture {
|
||||
return new EmptyFuture();
|
||||
}
|
||||
|
||||
requestComplete(content: nb.ICompleteRequest): Thenable<nb.ICompleteReplyMsg> {
|
||||
let response: Partial<nb.ICompleteReplyMsg> = {};
|
||||
return Promise.resolve(response as nb.ICompleteReplyMsg);
|
||||
}
|
||||
|
||||
interrupt(): Thenable<void> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
export class EmptyFuture implements FutureInternal {
|
||||
|
||||
|
||||
get inProgress(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
get msg(): nb.IMessage {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
get done(): Thenable<nb.IShellMessage> {
|
||||
let msg: nb.IShellMessage = {
|
||||
channel: 'shell',
|
||||
type: 'shell',
|
||||
content: runNotebookDisabled,
|
||||
header: undefined,
|
||||
metadata: undefined,
|
||||
parent_header: undefined
|
||||
};
|
||||
|
||||
return Promise.resolve(msg);
|
||||
}
|
||||
|
||||
sendInputReply(content: nb.IInputReply): void {
|
||||
// no-op
|
||||
}
|
||||
dispose() {
|
||||
// No-op
|
||||
}
|
||||
|
||||
setReplyHandler(handler: nb.MessageHandler<nb.IShellMessage>): void {
|
||||
// no-op
|
||||
}
|
||||
setStdInHandler(handler: nb.MessageHandler<nb.IStdinMessage>): void {
|
||||
// no-op
|
||||
}
|
||||
setIOPubHandler(handler: nb.MessageHandler<nb.IIOPubMessage>): void {
|
||||
setTimeout(() => {
|
||||
let msg: nb.IIOPubMessage = {
|
||||
channel: 'iopub',
|
||||
type: 'iopub',
|
||||
header: <nb.IHeader>{
|
||||
msg_id: '0',
|
||||
msg_type: 'error'
|
||||
},
|
||||
content: <nb.IErrorResult>{
|
||||
ename: localize('errorName', "Error"),
|
||||
evalue: runNotebookDisabled,
|
||||
output_type: 'error'
|
||||
},
|
||||
metadata: undefined,
|
||||
parent_header: undefined
|
||||
};
|
||||
handler.handle(msg);
|
||||
}, 10);
|
||||
}
|
||||
registerMessageHook(hook: (msg: nb.IIOPubMessage) => boolean | Thenable<boolean>): void {
|
||||
// no-op
|
||||
}
|
||||
removeMessageHook(hook: (msg: nb.IIOPubMessage) => boolean | Thenable<boolean>): void {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { nb } from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { SQL_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/browser/notebookService';
|
||||
import { LocalContentManager } from 'sql/workbench/services/notebook/common/localContentManager';
|
||||
import { SqlSessionManager } from 'sql/workbench/services/notebook/browser/sql/sqlSessionManager';
|
||||
|
||||
export class SqlNotebookManager implements nb.NotebookProvider {
|
||||
private _contentManager: nb.ContentManager;
|
||||
private _sessionManager: nb.SessionManager;
|
||||
|
||||
constructor(instantiationService: IInstantiationService) {
|
||||
this._contentManager = instantiationService.createInstance(LocalContentManager);
|
||||
this._sessionManager = new SqlSessionManager(instantiationService);
|
||||
}
|
||||
|
||||
public get providerId(): string {
|
||||
return SQL_NOTEBOOK_PROVIDER;
|
||||
}
|
||||
|
||||
public get contentManager(): nb.ContentManager {
|
||||
return this._contentManager;
|
||||
}
|
||||
|
||||
public get serverManager(): nb.ServerManager {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public get sessionManager(): nb.SessionManager {
|
||||
return this._sessionManager;
|
||||
}
|
||||
|
||||
getNotebookManager(notebookUri: vscode.Uri): Thenable<nb.NotebookManager> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
handleNotebookClosed(notebookUri: vscode.Uri): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
public get standardKernels(): nb.IStandardKernel[] {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { INotebookManager, INotebookProvider, SQL_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/browser/notebookService';
|
||||
import { SqlNotebookManager } from 'sql/workbench/services/notebook/browser/sql/sqlNotebookManager';
|
||||
|
||||
export class SqlNotebookProvider implements INotebookProvider {
|
||||
private manager: SqlNotebookManager;
|
||||
|
||||
constructor(private _instantiationService: IInstantiationService) {
|
||||
this.manager = new SqlNotebookManager(this._instantiationService);
|
||||
}
|
||||
|
||||
public get providerId(): string {
|
||||
return SQL_NOTEBOOK_PROVIDER;
|
||||
}
|
||||
|
||||
getNotebookManager(notebookUri: URI): Thenable<INotebookManager> {
|
||||
return Promise.resolve(this.manager);
|
||||
}
|
||||
|
||||
handleNotebookClosed(notebookUri: URI): void {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,641 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { nb, QueryExecuteSubsetResult, IDbColumn, BatchSummary, IResultMessage, ResultSetSummary } from 'azdata';
|
||||
import { localize } from 'vs/nls';
|
||||
import { FutureInternal, notebookConstants } from 'sql/workbench/parts/notebook/browser/models/modelInterfaces';
|
||||
import QueryRunner from 'sql/platform/query/common/queryRunner';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { Deferred } from 'sql/base/common/promise';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
import { escape } from 'sql/base/common/strings';
|
||||
import * as notebookUtils from 'sql/workbench/parts/notebook/browser/models/notebookUtils';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { ILanguageMagic } from 'sql/workbench/services/notebook/browser/notebookService';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { getUriPrefix, uriPrefixes } from 'sql/platform/connection/common/utils';
|
||||
|
||||
export const sqlKernelError: string = localize("sqlKernelError", "SQL kernel error");
|
||||
export const MAX_ROWS = 5000;
|
||||
export const NotebookConfigSectionName = 'notebook';
|
||||
export const MaxTableRowsConfigName = 'maxTableRows';
|
||||
|
||||
const languageMagics: ILanguageMagic[] = [{
|
||||
language: 'Python',
|
||||
magic: 'lang_python'
|
||||
}, {
|
||||
language: 'R',
|
||||
magic: 'lang_r'
|
||||
}, {
|
||||
language: 'Java',
|
||||
magic: 'lang_java'
|
||||
}];
|
||||
|
||||
export interface SQLData {
|
||||
columns: Array<string>;
|
||||
rows: Array<Array<string>>;
|
||||
}
|
||||
|
||||
export class SqlSessionManager implements nb.SessionManager {
|
||||
private static _sessions: nb.ISession[] = [];
|
||||
|
||||
constructor(private _instantiationService: IInstantiationService) { }
|
||||
|
||||
public get isReady(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public get ready(): Thenable<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public get specs(): nb.IAllKernels {
|
||||
let allKernels: nb.IAllKernels = {
|
||||
defaultKernel: notebookConstants.sqlKernel,
|
||||
kernels: [notebookConstants.sqlKernelSpec]
|
||||
};
|
||||
return allKernels;
|
||||
}
|
||||
|
||||
startNew(options: nb.ISessionOptions): Thenable<nb.ISession> {
|
||||
let sqlSession = new SqlSession(options, this._instantiationService);
|
||||
let index = SqlSessionManager._sessions.findIndex(session => session.path === options.path);
|
||||
if (index > -1) {
|
||||
SqlSessionManager._sessions.splice(index);
|
||||
}
|
||||
SqlSessionManager._sessions.push(sqlSession);
|
||||
return Promise.resolve(sqlSession);
|
||||
}
|
||||
|
||||
shutdown(id: string): Thenable<void> {
|
||||
let index = SqlSessionManager._sessions.findIndex(session => session.id === id);
|
||||
if (index > -1) {
|
||||
let sessionManager = SqlSessionManager._sessions[index];
|
||||
SqlSessionManager._sessions.splice(index);
|
||||
if (sessionManager && sessionManager.kernel) {
|
||||
let sqlKernel = sessionManager.kernel as SqlKernel;
|
||||
return sqlKernel.disconnect();
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
export class SqlSession implements nb.ISession {
|
||||
private _kernel: SqlKernel;
|
||||
private _defaultKernelLoaded = false;
|
||||
private _currentConnection: IConnectionProfile;
|
||||
|
||||
public set defaultKernelLoaded(value) {
|
||||
this._defaultKernelLoaded = value;
|
||||
}
|
||||
|
||||
public get defaultKernelLoaded(): boolean {
|
||||
return this._defaultKernelLoaded;
|
||||
}
|
||||
|
||||
constructor(private options: nb.ISessionOptions, private _instantiationService: IInstantiationService) {
|
||||
this._kernel = this._instantiationService.createInstance(SqlKernel, options.path);
|
||||
}
|
||||
|
||||
public get canChangeKernels(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this.options.kernelId || this.kernel ? this._kernel.id : '';
|
||||
}
|
||||
|
||||
public get path(): string {
|
||||
return this.options.path;
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
return this.options.name || '';
|
||||
}
|
||||
|
||||
public get type(): string {
|
||||
return this.options.type || '';
|
||||
}
|
||||
|
||||
public get status(): nb.KernelStatus {
|
||||
return 'connected';
|
||||
}
|
||||
|
||||
public get kernel(): nb.IKernel {
|
||||
return this._kernel;
|
||||
}
|
||||
|
||||
changeKernel(kernelInfo: nb.IKernelSpec): Thenable<nb.IKernel> {
|
||||
return Promise.resolve(this.kernel);
|
||||
}
|
||||
|
||||
configureKernel(kernelInfo: nb.IKernelSpec): Thenable<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
configureConnection(connection: ConnectionProfile): Thenable<void> {
|
||||
if (this._kernel) {
|
||||
this._kernel.connection = connection;
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
class SqlKernel extends Disposable implements nb.IKernel {
|
||||
private _queryRunner: QueryRunner;
|
||||
private _currentConnection: IConnectionProfile;
|
||||
private _currentConnectionProfile: ConnectionProfile;
|
||||
static kernelId: number = 0;
|
||||
|
||||
private _id: string;
|
||||
private _future: SQLFuture;
|
||||
private _executionCount: number = 0;
|
||||
private _magicToExecutorMap = new Map<string, ExternalScriptMagic>();
|
||||
private _connectionPath: string;
|
||||
|
||||
constructor(private _path: string,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IErrorMessageService private _errorMessageService: IErrorMessageService,
|
||||
@IConfigurationService private _configurationService: IConfigurationService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService
|
||||
) {
|
||||
super();
|
||||
this.initMagics();
|
||||
this.setConnectionPath();
|
||||
}
|
||||
|
||||
private initMagics(): void {
|
||||
for (let magic of languageMagics) {
|
||||
let scriptMagic = new ExternalScriptMagic(magic.language);
|
||||
this._magicToExecutorMap.set(magic.magic, scriptMagic);
|
||||
}
|
||||
}
|
||||
|
||||
private setConnectionPath(): void {
|
||||
if (this._path) {
|
||||
let prefix = getUriPrefix(this._path);
|
||||
if (!prefix || prefix === uriPrefixes.connection) {
|
||||
this._connectionPath = uriPrefixes.notebook.concat(this._path);
|
||||
} else if (prefix !== uriPrefixes.notebook) {
|
||||
try {
|
||||
let uri = URI.parse(this._path);
|
||||
if (uri && uri.scheme) {
|
||||
this._connectionPath = uri.toString().replace(uri.scheme, uriPrefixes.notebook);
|
||||
}
|
||||
} catch {
|
||||
// Ignore exceptions from URI parsing
|
||||
} finally {
|
||||
// If _connectionPath hasn't been set yet, set _connectionPath to _path as a last resort
|
||||
if (!this._connectionPath) {
|
||||
this._connectionPath = this._path;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
if (this._id === undefined) {
|
||||
this._id = (SqlKernel.kernelId++).toString();
|
||||
}
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
return notebookConstants.sqlKernel;
|
||||
}
|
||||
|
||||
public get supportsIntellisense(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public get requiresConnection(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public get isReady(): boolean {
|
||||
// should we be checking on the tools service status here?
|
||||
return true;
|
||||
}
|
||||
|
||||
public get ready(): Thenable<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public get info(): nb.IInfoReply {
|
||||
let info: nb.IInfoReply = {
|
||||
protocol_version: '',
|
||||
implementation: '',
|
||||
implementation_version: '',
|
||||
language_info: {
|
||||
name: 'sql',
|
||||
version: '',
|
||||
},
|
||||
banner: '',
|
||||
help_links: [{
|
||||
text: '',
|
||||
url: ''
|
||||
}]
|
||||
};
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public set connection(conn: IConnectionProfile) {
|
||||
this._currentConnection = conn;
|
||||
this._currentConnectionProfile = new ConnectionProfile(this._capabilitiesService, this._currentConnection);
|
||||
this._queryRunner = undefined;
|
||||
}
|
||||
|
||||
getSpec(): Thenable<nb.IKernelSpec> {
|
||||
return Promise.resolve(notebookConstants.sqlKernelSpec);
|
||||
}
|
||||
|
||||
requestExecute(content: nb.IExecuteRequest, disposeOnDone?: boolean): nb.IFuture {
|
||||
let canRun: boolean = true;
|
||||
let code = this.getCodeWithoutCellMagic(content);
|
||||
if (this._queryRunner) {
|
||||
// Cancel any existing query
|
||||
if (this._future && !this._queryRunner.hasCompleted) {
|
||||
this._queryRunner.cancelQuery().then(ok => undefined, error => this._errorMessageService.showDialog(Severity.Error, sqlKernelError, error));
|
||||
// TODO when we can just show error as an output, should show an "execution canceled" error in output
|
||||
this._future.handleDone();
|
||||
}
|
||||
this._queryRunner.runQuery(code);
|
||||
} else if (this._currentConnection && this._currentConnectionProfile) {
|
||||
this._queryRunner = this._instantiationService.createInstance(QueryRunner, this._connectionPath);
|
||||
this._connectionManagementService.connect(this._currentConnectionProfile, this._connectionPath).then((result) => {
|
||||
this.addQueryEventListeners(this._queryRunner);
|
||||
this._queryRunner.runQuery(code);
|
||||
});
|
||||
} else {
|
||||
canRun = false;
|
||||
}
|
||||
|
||||
// Only update execution count if this will run. if not, set as undefined in future so cell isn't shown as having run?
|
||||
// TODO verify this is "canonical" behavior
|
||||
let count = canRun ? ++this._executionCount : undefined;
|
||||
|
||||
this._future = new SQLFuture(this._queryRunner, count, this._configurationService, this.logService);
|
||||
if (!canRun) {
|
||||
// Complete early
|
||||
this._future.handleDone(new Error(localize('connectionRequired', "A connection must be chosen to run notebook cells")));
|
||||
}
|
||||
|
||||
// TODO should we cleanup old future? I don't think we need to
|
||||
return this._future;
|
||||
}
|
||||
|
||||
private getCodeWithoutCellMagic(content: nb.IExecuteRequest): string {
|
||||
let code = Array.isArray(content.code) ? content.code.join('') : content.code;
|
||||
let firstLineEnd = code.indexOf(this.textResourcePropertiesService.getEOL(URI.file(this._path)));
|
||||
let firstLine = code.substring(0, (firstLineEnd >= 0) ? firstLineEnd : 0).trimLeft();
|
||||
if (firstLine.startsWith('%%')) {
|
||||
// Strip out the line
|
||||
code = code.substring(firstLineEnd, code.length);
|
||||
// Try and match to an external script magic. If we add more magics later, should handle transforms better
|
||||
let magic = notebookUtils.tryMatchCellMagic(firstLine);
|
||||
if (magic) {
|
||||
let executor = this._magicToExecutorMap.get(magic.toLowerCase());
|
||||
if (executor) {
|
||||
code = executor.convertToExternalScript(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
requestComplete(content: nb.ICompleteRequest): Thenable<nb.ICompleteReplyMsg> {
|
||||
let response: Partial<nb.ICompleteReplyMsg> = {};
|
||||
return Promise.resolve(response as nb.ICompleteReplyMsg);
|
||||
}
|
||||
|
||||
interrupt(): Thenable<void> {
|
||||
// TODO: figure out what to do with the QueryCancelResult
|
||||
return this._queryRunner.cancelQuery().then((cancelResult) => {
|
||||
});
|
||||
}
|
||||
|
||||
private addQueryEventListeners(queryRunner: QueryRunner): void {
|
||||
this._register(queryRunner.onQueryEnd(() => {
|
||||
this.queryComplete().catch(error => {
|
||||
this._errorMessageService.showDialog(Severity.Error, sqlKernelError, error);
|
||||
});
|
||||
}));
|
||||
this._register(queryRunner.onMessage(message => {
|
||||
// TODO handle showing a messages output (should be updated with all messages, only changing 1 output in total)
|
||||
if (this._future && isUndefinedOrNull(message.selection)) {
|
||||
this._future.handleMessage(message);
|
||||
}
|
||||
}));
|
||||
this._register(queryRunner.onBatchEnd(batch => {
|
||||
if (this._future) {
|
||||
this._future.handleBatchEnd(batch);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private async queryComplete(): Promise<void> {
|
||||
if (this._future) {
|
||||
this._future.handleDone();
|
||||
}
|
||||
// TODO issue #2746 should ideally show a warning inside the dialog if have no data
|
||||
}
|
||||
|
||||
public async disconnect(): Promise<void> {
|
||||
if (this._connectionPath) {
|
||||
if (this._connectionManagementService.isConnected(this._connectionPath)) {
|
||||
try {
|
||||
await this._connectionManagementService.disconnect(this._connectionPath);
|
||||
} catch (err) {
|
||||
this.logService.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export class SQLFuture extends Disposable implements FutureInternal {
|
||||
private _msg: nb.IMessage = undefined;
|
||||
private ioHandler: nb.MessageHandler<nb.IIOPubMessage>;
|
||||
private doneHandler: nb.MessageHandler<nb.IShellMessage>;
|
||||
private doneDeferred = new Deferred<nb.IShellMessage>();
|
||||
private configuredMaxRows: number = MAX_ROWS;
|
||||
private _outputAddedPromises: Promise<void>[] = [];
|
||||
constructor(
|
||||
private _queryRunner: QueryRunner,
|
||||
private _executionCount: number | undefined,
|
||||
configurationService: IConfigurationService,
|
||||
private readonly logService: ILogService
|
||||
) {
|
||||
super();
|
||||
let config = configurationService.getValue(NotebookConfigSectionName);
|
||||
if (config) {
|
||||
let maxRows = config[MaxTableRowsConfigName] ? config[MaxTableRowsConfigName] : undefined;
|
||||
if (maxRows && maxRows > 0) {
|
||||
this.configuredMaxRows = maxRows;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get inProgress(): boolean {
|
||||
return this._queryRunner && !this._queryRunner.hasCompleted;
|
||||
}
|
||||
set inProgress(val: boolean) {
|
||||
if (this._queryRunner && !val) {
|
||||
this._queryRunner.cancelQuery();
|
||||
}
|
||||
}
|
||||
get msg(): nb.IMessage {
|
||||
return this._msg;
|
||||
}
|
||||
|
||||
get done(): Thenable<nb.IShellMessage> {
|
||||
return this.doneDeferred.promise;
|
||||
}
|
||||
|
||||
public handleDone(err?: Error): void {
|
||||
this.handleDoneAsync(err);
|
||||
// TODO we should reject where some failure happened?
|
||||
}
|
||||
|
||||
private async handleDoneAsync(err?: Error): Promise<void> {
|
||||
// must wait on all outstanding output updates to complete
|
||||
if (this._outputAddedPromises && this._outputAddedPromises.length > 0) {
|
||||
// Do not care about error handling as this is handled elsewhere
|
||||
await Promise.all(this._outputAddedPromises).catch((err) => undefined);
|
||||
}
|
||||
let msg: nb.IExecuteReplyMsg = {
|
||||
channel: 'shell',
|
||||
type: 'execute_reply',
|
||||
content: {
|
||||
status: 'ok',
|
||||
execution_count: this._executionCount
|
||||
},
|
||||
header: undefined,
|
||||
metadata: {},
|
||||
parent_header: undefined
|
||||
};
|
||||
this._msg = msg;
|
||||
if (this.doneHandler) {
|
||||
this.doneHandler.handle(msg);
|
||||
}
|
||||
this.doneDeferred.resolve(msg);
|
||||
}
|
||||
|
||||
sendInputReply(content: nb.IInputReply): void {
|
||||
// no-op
|
||||
}
|
||||
|
||||
setReplyHandler(handler: nb.MessageHandler<nb.IShellMessage>): void {
|
||||
// no-op
|
||||
}
|
||||
setStdInHandler(handler: nb.MessageHandler<nb.IStdinMessage>): void {
|
||||
// no-op
|
||||
}
|
||||
|
||||
public handleMessage(msg: IResultMessage | string): void {
|
||||
if (this.ioHandler) {
|
||||
let message;
|
||||
if (typeof msg === 'string') {
|
||||
message = this.convertToDisplayMessage(msg);
|
||||
}
|
||||
else {
|
||||
if (msg.isError) {
|
||||
message = this.convertToError(msg);
|
||||
} else {
|
||||
message = this.convertToDisplayMessage(msg);
|
||||
}
|
||||
}
|
||||
this.ioHandler.handle(message);
|
||||
}
|
||||
}
|
||||
|
||||
public handleBatchEnd(batch: BatchSummary): void {
|
||||
if (this.ioHandler) {
|
||||
this._outputAddedPromises.push(this.processResultSets(batch));
|
||||
}
|
||||
}
|
||||
|
||||
private async processResultSets(batch: BatchSummary): Promise<void> {
|
||||
try {
|
||||
for (let resultSet of batch.resultSetSummaries) {
|
||||
let rowCount = resultSet.rowCount > this.configuredMaxRows ? this.configuredMaxRows : resultSet.rowCount;
|
||||
if (rowCount === this.configuredMaxRows) {
|
||||
this.handleMessage(localize('sqlMaxRowsDisplayed', "Displaying Top {0} rows.", rowCount));
|
||||
}
|
||||
await this.sendResultSetAsIOPub(rowCount, resultSet);
|
||||
}
|
||||
} catch (err) {
|
||||
// TODO should we output this somewhere else?
|
||||
this.logService.error(`Error outputting result sets from Notebook query: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async sendResultSetAsIOPub(rowCount: number, resultSet: ResultSetSummary): Promise<void> {
|
||||
let subsetResult: QueryExecuteSubsetResult;
|
||||
if (rowCount > 0) {
|
||||
subsetResult = await this._queryRunner.getQueryRows(0, rowCount, resultSet.batchId, resultSet.id);
|
||||
} else {
|
||||
subsetResult = { message: '', resultSubset: { rowCount: 0, rows: [] } };
|
||||
}
|
||||
let msg: nb.IIOPubMessage = {
|
||||
channel: 'iopub',
|
||||
type: 'iopub',
|
||||
header: <nb.IHeader>{
|
||||
msg_id: undefined,
|
||||
msg_type: 'execute_result'
|
||||
},
|
||||
content: <nb.IExecuteResult>{
|
||||
output_type: 'execute_result',
|
||||
metadata: {},
|
||||
execution_count: this._executionCount,
|
||||
data: {
|
||||
'application/vnd.dataresource+json': this.convertToDataResource(resultSet.columnInfo, subsetResult),
|
||||
'text/html': this.convertToHtmlTable(resultSet.columnInfo, subsetResult)
|
||||
}
|
||||
},
|
||||
metadata: undefined,
|
||||
parent_header: undefined
|
||||
};
|
||||
this.ioHandler.handle(msg);
|
||||
}
|
||||
|
||||
setIOPubHandler(handler: nb.MessageHandler<nb.IIOPubMessage>): void {
|
||||
this.ioHandler = handler;
|
||||
}
|
||||
|
||||
registerMessageHook(hook: (msg: nb.IIOPubMessage) => boolean | Thenable<boolean>): void {
|
||||
// no-op
|
||||
}
|
||||
removeMessageHook(hook: (msg: nb.IIOPubMessage) => boolean | Thenable<boolean>): void {
|
||||
// no-op
|
||||
}
|
||||
|
||||
private convertToDataResource(columns: IDbColumn[], subsetResult: QueryExecuteSubsetResult): IDataResource {
|
||||
let columnsResources: IDataResourceSchema[] = [];
|
||||
columns.forEach(column => {
|
||||
columnsResources.push({ name: escape(column.columnName) });
|
||||
});
|
||||
let columnsFields: IDataResourceFields = { fields: undefined };
|
||||
columnsFields.fields = columnsResources;
|
||||
return {
|
||||
schema: columnsFields,
|
||||
data: subsetResult.resultSubset.rows.map(row => {
|
||||
let rowObject: { [key: string]: any; } = {};
|
||||
row.forEach((val, index) => {
|
||||
rowObject[index] = val.displayValue;
|
||||
});
|
||||
return rowObject;
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
private convertToHtmlTable(columns: IDbColumn[], d: QueryExecuteSubsetResult): string {
|
||||
let htmlString = '<table>';
|
||||
if (columns.length > 0) {
|
||||
htmlString += '<tr>';
|
||||
for (let column of columns) {
|
||||
htmlString += '<th>' + escape(column.columnName) + '</th>';
|
||||
}
|
||||
htmlString += '</tr>';
|
||||
}
|
||||
for (const row of d.resultSubset.rows) {
|
||||
htmlString += '<tr>';
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
htmlString += '<td>' + escape(row[i].displayValue) + '</td>';
|
||||
}
|
||||
htmlString += '</tr>';
|
||||
}
|
||||
htmlString += '</table>';
|
||||
return htmlString;
|
||||
}
|
||||
|
||||
private convertToDisplayMessage(msg: IResultMessage | string): nb.IIOPubMessage {
|
||||
if (msg) {
|
||||
let msgData = typeof msg === 'string' ? msg : msg.message;
|
||||
return {
|
||||
channel: 'iopub',
|
||||
type: 'iopub',
|
||||
header: <nb.IHeader>{
|
||||
msg_id: undefined,
|
||||
msg_type: 'display_data'
|
||||
},
|
||||
content: <nb.IDisplayData>{
|
||||
output_type: 'display_data',
|
||||
data: { 'text/html': msgData },
|
||||
metadata: {}
|
||||
},
|
||||
metadata: undefined,
|
||||
parent_header: undefined
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private convertToError(msg: IResultMessage | string): nb.IIOPubMessage {
|
||||
if (msg) {
|
||||
let msgData = typeof msg === 'string' ? msg : msg.message;
|
||||
return {
|
||||
channel: 'iopub',
|
||||
type: 'iopub',
|
||||
header: <nb.IHeader>{
|
||||
msg_id: undefined,
|
||||
msg_type: 'error'
|
||||
},
|
||||
content: <nb.IErrorResult>{
|
||||
output_type: 'error',
|
||||
evalue: msgData,
|
||||
ename: '',
|
||||
traceback: []
|
||||
},
|
||||
metadata: undefined,
|
||||
parent_header: undefined
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IDataResource {
|
||||
schema: IDataResourceFields;
|
||||
data: any[];
|
||||
}
|
||||
|
||||
export interface IDataResourceFields {
|
||||
fields: IDataResourceSchema[];
|
||||
}
|
||||
|
||||
export interface IDataResourceSchema {
|
||||
name: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
class ExternalScriptMagic {
|
||||
|
||||
constructor(private language: string) {
|
||||
}
|
||||
|
||||
public convertToExternalScript(script: string): string {
|
||||
return `execute sp_execute_external_script
|
||||
@language = N'${this.language}',
|
||||
@script = N'${script}'
|
||||
`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user