SQL Operations Studio Public Preview 1 (0.23) release source code

This commit is contained in:
Karl Burtram
2017-11-09 14:30:27 -08:00
parent b88ecb8d93
commit 3cdac41339
8829 changed files with 759707 additions and 286 deletions

View File

@@ -0,0 +1,183 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TPromise } from 'vs/base/common/winjs.base';
import { EditorInput, EditorModel } from 'vs/workbench/common/editor';
import { IConnectionManagementService, IConnectableInput, INewConnectionParams } from 'sql/parts/connection/common/connectionManagement';
import { IMessageService, Severity } from 'vs/platform/message/common/message';
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import Event, { Emitter } from 'vs/base/common/event';
import { EditSessionReadyParams } from 'data';
import URI from 'vs/base/common/uri';
import nls = require('vs/nls');
/**
* Input for the EditDataEditor. This input is simply a wrapper around a QueryResultsInput for the QueryResultsEditor
*/
export class EditDataInput extends EditorInput implements IConnectableInput {
public static ID: string = 'workbench.editorinputs.editDataInput';
private _visible: boolean;
private _hasBootstrapped: boolean;
private _editorContainer: HTMLElement;
private _updateTaskbar: Emitter<EditDataInput>;
private _editorInitializing: Emitter<boolean>;
private _showTableView: Emitter<EditDataInput>;
private _refreshButtonEnabled: boolean;
private _stopButtonEnabled: boolean;
private _setup: boolean;
private _toDispose: IDisposable[];
private _rowLimit: number;
private _objectType: string;
constructor(private _uri: URI, private _schemaName, private _tableName,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IQueryModelService private _queryModelService: IQueryModelService,
@IMessageService private _messageService: IMessageService
) {
super();
this._visible = false;
this._hasBootstrapped = false;
this._updateTaskbar = new Emitter<EditDataInput>();
this._showTableView = new Emitter<EditDataInput>();
this._editorInitializing = new Emitter<boolean>();
this._setup = false;
this._stopButtonEnabled = false;
this._refreshButtonEnabled = false;
this._toDispose = [];
//TODO determine is this is a table or a view
this._objectType = 'TABLE';
// Attach to event callbacks
if (this._queryModelService) {
let self = this;
// Register callbacks for the Actions
this._toDispose.push(
this._queryModelService.onRunQueryStart(uri => {
if (self.uri === uri) {
self.initEditStart();
}
})
);
this._toDispose.push(
this._queryModelService.onEditSessionReady((result) => {
if (self.uri === result.ownerUri) {
self.initEditEnd(result);
}
})
);
}
}
// Getters/Setters
public get tableName(): string { return this._tableName; }
public get schemaName(): string { return this._schemaName; }
public get uri(): string { return this._uri.toString(); }
public get updateTaskbar(): Event<EditDataInput> { return this._updateTaskbar.event; }
public get editorInitializing(): Event<boolean> { return this._editorInitializing.event; }
public get showTableView(): Event<EditDataInput> { return this._showTableView.event; }
public get stopButtonEnabled(): boolean { return this._stopButtonEnabled; }
public get refreshButtonEnabled(): boolean { return this._refreshButtonEnabled; }
public get container(): HTMLElement { return this._editorContainer; }
public get hasBootstrapped(): boolean { return this._hasBootstrapped; }
public get visible(): boolean { return this._visible; }
public get setup(): boolean { return this._setup; }
public get rowLimit(): number { return this._rowLimit; }
public get objectType(): string { return this._objectType; }
public getTypeId(): string { return EditDataInput.ID; }
public setVisibleTrue(): void { this._visible = true; }
public setBootstrappedTrue(): void { this._hasBootstrapped = true; }
public getResource(): URI { return this._uri; }
public getName(): string { return this._uri.path; }
public supportsSplitEditor(): boolean { return false; }
public setupComplete() { this._setup = true; }
public set container(container: HTMLElement) {
this._disposeContainer();
this._editorContainer = container;
}
// State Update Callbacks
public initEditStart(): void {
this._editorInitializing.fire(true);
this._refreshButtonEnabled = false;
this._stopButtonEnabled = true;
this._updateTaskbar.fire(this);
}
public initEditEnd(result: EditSessionReadyParams): void {
if (result.success) {
this._refreshButtonEnabled = true;
this._stopButtonEnabled = false;
} else {
this._refreshButtonEnabled = false;
this._stopButtonEnabled = false;
this._messageService.show(Severity.Error, result.message);
}
this._editorInitializing.fire(false);
this._updateTaskbar.fire(this);
}
public onConnectStart(): void {
// TODO: Indicate connection started
}
public onConnectReject(error?: string): void {
if (error) {
this._messageService.show(Severity.Error, nls.localize('connectionFailure', 'Edit Data Session Failed To Connect'));
}
}
public onConnectSuccess(params?: INewConnectionParams): void {
this._queryModelService.initializeEdit(this.uri, this.schemaName, this.tableName, this._objectType, this._rowLimit);
this._showTableView.fire(this);
}
public onDisconnect(): void {
// TODO: deal with disconnections
}
public onRowDropDownSet(rows: number) {
this._rowLimit = rows;
}
// Boiler Plate Functions
public matches(otherInput: any): boolean {
if (otherInput instanceof EditDataInput) {
return (this.uri === otherInput.uri);
}
return false;
}
public resolve(refresh?: boolean): TPromise<EditorModel> {
return TPromise.as(null);
}
public dispose(): void {
this._toDispose = dispose(this._toDispose);
this._disposeContainer();
super.dispose();
}
private _disposeContainer() {
if (this._editorContainer && this._editorContainer.parentElement) {
this._editorContainer.parentElement.removeChild(this._editorContainer);
this._editorContainer = null;
}
}
public close(): void {
// Dispose our edit session then disconnect our input
this._queryModelService.disposeEdit(this.uri).then(() => {
return this._connectionManagementService.disconnectEditor(this, true);
}).then(() => {
this.dispose();
super.close();
});
}
}

View File

@@ -0,0 +1,359 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!sql/parts/query/editor/media/queryEditor';
import { TPromise } from 'vs/base/common/winjs.base';
import * as DOM from 'vs/base/browser/dom';
import * as nls from 'vs/nls';
import { Builder, Dimension } from 'vs/base/browser/builder';
import { EditorOptions } from 'vs/workbench/common/editor';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { Position } from 'vs/platform/editor/common/editor';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { EditDataInput } from 'sql/parts/editData/common/editDataInput';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { Taskbar, ITaskbarContent } from 'sql/base/browser/ui/taskbar/taskbar';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { Action } from 'vs/base/common/actions';
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
import { IEditorDescriptorService } from 'sql/parts/query/editor/editorDescriptorService';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import {
RefreshTableAction, StopRefreshTableAction,
ChangeMaxRowsAction, ChangeMaxRowsActionItem
} from 'sql/parts/editData/execution/editDataActions';
import { EditDataModule } from 'sql/parts/grid/views/editData/editData.module';
import { IBootstrapService } from 'sql/services/bootstrap/bootstrapService';
import { EDITDATA_SELECTOR } from 'sql/parts/grid/views/editData/editData.component';
import { EditDataComponentParams } from 'sql/services/bootstrap/bootstrapParams';
/**
* Editor that hosts an action bar and a resultSetInput for an edit data session
*/
export class EditDataEditor extends BaseEditor {
public static ID: string = 'workbench.editor.editDataEditor';
private _dimension: Dimension;
private _container: HTMLElement;
private _taskbar: Taskbar;
private _taskbarContainer: HTMLElement;
private _changeMaxRowsActionItem: ChangeMaxRowsActionItem;
private _stopRefreshTableAction: StopRefreshTableAction;
private _refreshTableAction: RefreshTableAction;
private _changeMaxRowsAction: ChangeMaxRowsAction;
private _spinnerElement: HTMLElement;
private _initialized: boolean = false;
constructor(
@ITelemetryService _telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService,
@IContextMenuService private _contextMenuService: IContextMenuService,
@IQueryModelService private _queryModelService: IQueryModelService,
@IEditorDescriptorService private _editorDescriptorService: IEditorDescriptorService,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IBootstrapService private _bootstrapService: IBootstrapService
) {
super(EditDataEditor.ID, _telemetryService, themeService);
}
// PUBLIC METHODS ////////////////////////////////////////////////////////////
// Getters and Setters
public get editDataInput(): EditDataInput { return <EditDataInput>this.input; }
public get uri(): string { return this.input ? this.editDataInput.uri.toString() : undefined; }
public get tableName(): string { return this.editDataInput.tableName; }
/**
* Called to create the editor in the parent builder.
*/
public createEditor(parent: Builder): void {
const parentElement = parent.getHTMLElement();
DOM.addClass(parentElement, 'side-by-side-editor');
this._createTaskbar(parentElement);
this._container = document.createElement('div');
this._container.style.height = 'calc(100% - 28px)';
DOM.append(parentElement, this._container);
}
/**
* Sets the input data for this editor.
*/
public setInput(newInput: EditDataInput, options?: EditorOptions): TPromise<void> {
let oldInput = <EditDataInput>this.input;
if (!newInput.setup) {
this._initialized = false;
this._register(newInput.updateTaskbar((owner) => this._updateTaskbar(owner)));
this._register(newInput.editorInitializing((initializing) => this.onEditorInitializingChanged(initializing)));
this._register(newInput.showTableView(() => this._showTableView()));
newInput.onRowDropDownSet(this._changeMaxRowsActionItem.defaultRowCount);
newInput.setupComplete();
}
return super.setInput(newInput, options)
.then(() => this._updateInput(oldInput, newInput, options));
}
private onEditorInitializingChanged(initializing: boolean): void {
if (initializing) {
this.showSpinner();
} else {
this._initialized = true;
this.hideSpinner();
}
}
/**
* Show the spinner element that shows something is happening, hidden by default
*/
public showSpinner(): void {
setTimeout(() => {
if (!this._initialized) {
this._spinnerElement.style.visibility = 'visible';
}
}, 200);
}
/**
* Hide the spinner element to show that something was happening, hidden by default
*/
public hideSpinner(): void {
this._spinnerElement.style.visibility = 'hidden';
}
/**
* Sets this editor and the sub-editors to visible.
*/
public setEditorVisible(visible: boolean, position: Position): void {
super.setEditorVisible(visible, position);
}
/**
* Changes the position of the editor.
*/
public changePosition(position: Position): void {
super.changePosition(position);
}
/**
* Called to indicate to the editor that the input should be cleared and resources associated with the
* input should be freed.
*/
public clearInput(): void {
this._disposeEditors();
super.clearInput();
}
/**
* Updates the internal variable keeping track of the editor's size, and re-calculates the sash position.
* To be called when the container of this editor changes size.
*/
public layout(dimension: Dimension): void {
this._dimension = dimension;
let input: EditDataInput = <EditDataInput>this.input;
if (input) {
let uri: string = input.uri;
if (uri) {
this._queryModelService.resizeResultsets(uri);
}
}
}
public dispose(): void {
this._disposeEditors();
super.dispose();
}
public close(): void {
this.input.close();
}
/**
* Returns true if the results table for the current edit data session is visible
* Public for testing only.
*/
public _isResultsEditorVisible(): boolean {
if (!this.editDataInput) {
return false;
}
return this.editDataInput.visible;
}
/**
* Makes visible the results table for the current edit data session
*/
private _showTableView(): void {
if (this._isResultsEditorVisible()) {
return;
}
this._createTableViewContainer();
this._setTableViewVisible();
this.setInput(this.editDataInput, this.options);
}
// PRIVATE METHODS ////////////////////////////////////////////////////////////
private _updateTaskbar(owner: EditDataInput): void {
// Update the taskbar if the owner of this call is being presented
if (owner.matches(this.editDataInput)) {
this._refreshTableAction.enabled = owner.refreshButtonEnabled;
this._stopRefreshTableAction.enabled = owner.stopButtonEnabled;
this._changeMaxRowsActionItem.setCurrentOptionIndex = owner.rowLimit;
}
}
private _createTaskbar(parentElement: HTMLElement): void {
// Create QueryTaskbar
this._taskbarContainer = DOM.append(parentElement, DOM.$('div'));
this._taskbar = new Taskbar(this._taskbarContainer, this._contextMenuService, {
actionItemProvider: (action: Action) => this._getChangeMaxRowsAction(action)
});
// Create Actions for the toolbar
this._stopRefreshTableAction = this._instantiationService.createInstance(StopRefreshTableAction, this);
this._refreshTableAction = this._instantiationService.createInstance(RefreshTableAction, this);
this._changeMaxRowsAction = this._instantiationService.createInstance(ChangeMaxRowsAction, this);
// Create HTML Elements for the taskbar
let separator = Taskbar.createTaskbarSeparator();
let textSeperator = Taskbar.createTaskbarText(nls.localize('maxRowTaskbar', 'Max Rows:'));
this._spinnerElement = Taskbar.createTaskbarSpinner();
// Set the content in the order we desire
let content: ITaskbarContent[] = [
{ action: this._stopRefreshTableAction },
{ action: this._refreshTableAction },
{ element: separator },
{ element: textSeperator },
{ action: this._changeMaxRowsAction },
{ element: this._spinnerElement }
];
this._taskbar.setContent(content);
}
/**
* Gets the IActionItem for the list of row number drop down
*/
private _getChangeMaxRowsAction(action: Action): IActionItem {
let actionID = ChangeMaxRowsAction.ID;
if (action.id === actionID) {
if (!this._changeMaxRowsActionItem) {
this._changeMaxRowsActionItem = this._instantiationService.createInstance(ChangeMaxRowsActionItem, this);
}
return this._changeMaxRowsActionItem;
}
return null;
}
/**
* Handles setting input for this editor. If this new input does not match the old input (e.g. a new file
* has been opened with the same editor, or we are opening the editor for the first time).
*/
private _updateInput(oldInput: EditDataInput, newInput: EditDataInput, options?: EditorOptions): TPromise<void> {
let returnValue: TPromise<void>;
if (!newInput.matches(oldInput)) {
this._disposeEditors();
if (this._isResultsEditorVisible()) {
this._createTableViewContainer();
let uri: string = newInput.uri;
if (uri) {
this._queryModelService.refreshResultsets(uri);
}
}
returnValue = this._setNewInput(newInput, options);
} else {
this._setNewInput(newInput, options);
returnValue = TPromise.as(null);
}
this._updateTaskbar(newInput);
return returnValue;
}
/**
* Handles setting input and creating editors when this QueryEditor is either:
* - Opened for the first time
* - Opened with a new EditDataInput
*/
private _setNewInput(newInput: EditDataInput, options?: EditorOptions): TPromise<void> {
if (this._isResultsEditorVisible()) {
// If both editors exist, create a joined promise and wait for both editors to be created
return this._bootstrapAngularAndResults(newInput, options);
}
return TPromise.as(undefined);
}
/**
* Appends the HTML for the edit data table view
*/
private _createTableViewContainer() {
if (!this.editDataInput.container) {
this.editDataInput.container = DOM.append(this._container, DOM.$('.editDataContainer'));
this.editDataInput.container.style.height = '100%';
} else {
DOM.append(this._container, this.editDataInput.container);
}
}
private _disposeEditors(): void {
if (this._container) {
new Builder(this._container).clearChildren();
}
}
/**
* Load the angular components and record for this input that we have done so
*/
private _bootstrapAngularAndResults(input: EditDataInput, options: EditorOptions): TPromise<void> {
super.setInput(input, options);
if (!input.hasBootstrapped) {
let uri = this.editDataInput.uri;
// Pass the correct DataService to the new angular component
let dataService = this._queryModelService.getDataService(uri);
if (!dataService) {
throw new Error('DataService not found for URI: ' + uri);
}
// Mark that we have bootstrapped
input.setBootstrappedTrue();
// Get the bootstrap params and perform the bootstrap
// Note: pass in input so on disposal this is cleaned up.
// Otherwise many components will be left around and be subscribed
// to events from the backing data service
const parent = this.editDataInput.container;
let params: EditDataComponentParams = {
dataService: dataService
};
this._bootstrapService.bootstrap(
EditDataModule,
parent,
EDITDATA_SELECTOR,
params,
this.editDataInput);
}
return TPromise.as<void>(null);
}
private _setTableViewVisible(): void {
this.editDataInput.setVisibleTrue();
}
}

View File

@@ -0,0 +1,201 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Action, IActionItem, IActionRunner } from 'vs/base/common/actions';
import { TPromise } from 'vs/base/common/winjs.base';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
import { SelectBox } from 'vs/base/browser/ui/selectBox/selectBox';
import { EventEmitter } from 'vs/base/common/eventEmitter';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import { EditDataEditor } from 'sql/parts/editData/editor/editDataEditor';
import { IMessageService, Severity } from 'vs/platform/message/common/message';
import nls = require('vs/nls');
import * as dom from 'vs/base/browser/dom';
const $ = dom.$;
/**
* Action class that edit data based actions will extend
*/
export abstract class EditDataAction extends Action {
private _classes: string[];
constructor(protected editor: EditDataEditor, id: string, enabledClass: string,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService) {
super(id);
this.enabled = true;
this.setClass(enabledClass);
}
/**
* This method is executed when the button is clicked.
*/
public abstract run(): TPromise<void>;
protected setClass(enabledClass: string): void {
this._classes = [];
if (enabledClass) {
this._classes.push(enabledClass);
}
this.class = this._classes.join(' ');
}
/**
* Returns the URI of the given editor if it is not undefined and is connected.
*/
public isConnected(editor: EditDataEditor): boolean {
if (!editor || !editor.uri) {
return false;
}
return this._connectionManagementService.isConnected(editor.uri);
}
}
/**
* Action class that refreshes the table for an edit data session
*/
export class RefreshTableAction extends EditDataAction {
private static EnabledClass = 'refresh';
public static ID = 'refreshTableAction';
constructor(editor: EditDataEditor,
@IQueryModelService private _queryModelService: IQueryModelService,
@IConnectionManagementService _connectionManagementService: IConnectionManagementService,
@IMessageService private _messageService: IMessageService
) {
super(editor, RefreshTableAction.ID, RefreshTableAction.EnabledClass, _connectionManagementService);
this.label = nls.localize('refresh', 'Refresh');
}
public run(): TPromise<void> {
if (this.isConnected(this.editor)) {
let input = this.editor.editDataInput;
this._queryModelService.disposeEdit(input.uri).then((result) => {
this._queryModelService.initializeEdit(input.uri, input.schemaName, input.tableName, input.objectType, input.rowLimit);
}, error => {
this._messageService.show(Severity.Error, nls.localize('disposeEditFailure', 'Dispose Edit Failed With Error: ') + error);
});
}
return TPromise.as(null);
}
}
/**
* Action class that cancels the refresh data trigger in an edit data session
*/
export class StopRefreshTableAction extends EditDataAction {
private static EnabledClass = 'stop';
public static ID = 'stopRefreshAction';
constructor(editor: EditDataEditor,
@IQueryModelService private _queryModelService: IQueryModelService,
@IConnectionManagementService _connectionManagementService: IConnectionManagementService
) {
super(editor, StopRefreshTableAction.ID, StopRefreshTableAction.EnabledClass, _connectionManagementService);
this.enabled = false;
this.label = nls.localize('stop', 'Stop');
}
public run(): TPromise<void> {
let input = this.editor.editDataInput;
this._queryModelService.disposeEdit(input.uri);
return TPromise.as(null);
}
}
/**
* Action class that is tied with ChangeMaxRowsActionItem
*/
export class ChangeMaxRowsAction extends EditDataAction {
private static EnabledClass = '';
public static ID = 'changeMaxRowsAction';
constructor(editor: EditDataEditor,
@IQueryModelService private _queryModelService: IQueryModelService,
@IConnectionManagementService _connectionManagementService: IConnectionManagementService
) {
super(editor, ChangeMaxRowsAction.ID, undefined, _connectionManagementService);
this.enabled = false;
this.class = ChangeMaxRowsAction.EnabledClass;
}
public run(): TPromise<void> {
return TPromise.as(null);
}
}
/*
* Action item that handles the dropdown (combobox) that lists the avaliable number of row selections
* for an edit data session
*/
export class ChangeMaxRowsActionItem extends EventEmitter implements IActionItem {
public actionRunner: IActionRunner;
public defaultRowCount: number;
private container: HTMLElement;
private start: HTMLElement;
private selectBox: SelectBox;
private toDispose: IDisposable[];
private context: any;
private _options: string[];
private _currentOptionsIndex: number;
constructor(private _editor: EditDataEditor) {
super();
this._options = ['200', '1000', '10000'];
this._currentOptionsIndex = 0;
this.toDispose = [];
this.selectBox = new SelectBox([], -1);
this._registerListeners();
this._refreshOptions();
this.defaultRowCount = Number(this._options[this._currentOptionsIndex]);
}
public render(container: HTMLElement): void {
this.container = container;
this.selectBox.render(dom.append(container, $('.configuration.listDatabasesSelectBox')));
}
public setActionContext(context: any): void {
this.context = context;
}
public isEnabled(): boolean {
return true;
}
public set setCurrentOptionIndex(selection: number) {
this._currentOptionsIndex = this._options.findIndex(x => x === selection.toString());
this._refreshOptions();
}
public focus(): void {
this.start.focus();
}
public blur(): void {
this.container.blur();
}
public dispose(): void {
this.toDispose = dispose(this.toDispose);
}
private _refreshOptions(databaseIndex?: number): void {
this.selectBox.setOptions(this._options, this._currentOptionsIndex);
}
private _registerListeners(): void {
this.toDispose.push(this.selectBox.onDidSelect(selection => {
this._currentOptionsIndex = this._options.findIndex(x => x === selection.selected);
this._editor.editDataInput.onRowDropDownSet(Number(selection.selected));
}));
}
}