Add option for using generic SQL queries to filter EditData rows via a query editor pane. (#1329)

This commit is contained in:
Cory Rivera
2018-05-14 12:27:55 -07:00
committed by GitHub
parent 6b549696c5
commit 89c48bbe75
27 changed files with 1075 additions and 293 deletions

View File

@@ -131,6 +131,14 @@
background: url("filter_inverse.svg") center center no-repeat !important;
}
.vs .icon.filterLabel {
background-image: url("filter.svg");
}
.vs-dark .icon.filterLabel,
.hc-black .icon.filterLabel {
background-image: url("filter_inverse.svg");
}
.vs .icon.warning-badge,
.vs-dark .icon.warning-badge,

View File

@@ -4,19 +4,21 @@
*--------------------------------------------------------------------------------------------*/
import { TPromise } from 'vs/base/common/winjs.base';
import { EditorInput, EditorModel } from 'vs/workbench/common/editor';
import { EditorInput, EditorModel, ConfirmResult, EncodingMode } from 'vs/workbench/common/editor';
import { IConnectionManagementService, IConnectableInput, INewConnectionParams } from 'sql/parts/connection/common/connectionManagement';
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 'sqlops';
import { EditSessionReadyParams, ISelectionData } from 'sqlops';
import URI from 'vs/base/common/uri';
import nls = require('vs/nls');
import { INotificationService } from 'vs/platform/notification/common/notification';
import Severity from 'vs/base/common/severity';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput';
/**
* Input for the EditDataEditor. This input is simply a wrapper around a QueryResultsInput for the QueryResultsEditor
* Input for the EditDataEditor.
*/
export class EditDataInput extends EditorInput implements IConnectableInput {
public static ID: string = 'workbench.editorinputs.editDataInput';
@@ -25,15 +27,23 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
private _editorContainer: HTMLElement;
private _updateTaskbar: Emitter<EditDataInput>;
private _editorInitializing: Emitter<boolean>;
private _showTableView: Emitter<EditDataInput>;
private _showResultsEditor: Emitter<EditDataInput>;
private _refreshButtonEnabled: boolean;
private _stopButtonEnabled: boolean;
private _setup: boolean;
private _toDispose: IDisposable[];
private _rowLimit: number;
private _objectType: string;
private _css: HTMLStyleElement;
private _useQueryFilter: boolean;
constructor(private _uri: URI, private _schemaName, private _tableName,
constructor(
private _uri: URI,
private _schemaName,
private _tableName,
private _sql: UntitledEditorInput,
private _queryString: string,
private _results: EditDataResultsInput,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IQueryModelService private _queryModelService: IQueryModelService,
@INotificationService private notificationService: INotificationService
@@ -42,12 +52,20 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
this._visible = false;
this._hasBootstrapped = false;
this._updateTaskbar = new Emitter<EditDataInput>();
this._showTableView = new Emitter<EditDataInput>();
this._showResultsEditor = new Emitter<EditDataInput>();
this._editorInitializing = new Emitter<boolean>();
this._setup = false;
this._stopButtonEnabled = false;
this._refreshButtonEnabled = false;
this._toDispose = [];
this._useQueryFilter = false;
// re-emit sql editor events through this editor if it exists
if (this._sql) {
this._toDispose.push(this._sql.onDidChangeDirty(() => this._onDidChangeDirty.fire()));
this._sql.disableSaving();
}
this.disableSaving();
//TODO determine is this is a table or a view
this._objectType = 'TABLE';
@@ -79,27 +97,45 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
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 sql(): UntitledEditorInput { return this._sql; }
public get results(): EditDataResultsInput { return this._results; }
public getResultsInputResource(): string { return this._results.uri; }
public get updateTaskbarEvent(): Event<EditDataInput> { return this._updateTaskbar.event; }
public get editorInitializingEvent(): Event<boolean> { return this._editorInitializing.event; }
public get showResultsEditorEvent(): Event<EditDataInput> { return this._showResultsEditor.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 showResultsEditor(): void { this._showResultsEditor.fire(); }
public isDirty(): boolean { return false; }
public save(): TPromise<boolean> { return TPromise.as(false); }
public confirmSave(): TPromise<ConfirmResult> { return TPromise.wrap(ConfirmResult.DONT_SAVE); }
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;
public get queryString(): string {
return this._queryString;
}
public set queryString(queryString: string) {
this._queryString = queryString;
}
public get css(): HTMLStyleElement {
return this._css;
}
public set css(css: HTMLStyleElement) {
this._css = css;
}
public get queryPaneEnabled(): boolean {
return this._useQueryFilter;
}
public set queryPaneEnabled(useQueryFilter: boolean) {
this._useQueryFilter = useQueryFilter;
}
// State Update Callbacks
@@ -111,13 +147,9 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
}
public initEditEnd(result: EditSessionReadyParams): void {
if (result.success) {
this._refreshButtonEnabled = true;
this._stopButtonEnabled = false;
} else {
this._refreshButtonEnabled = false;
this._stopButtonEnabled = false;
this._refreshButtonEnabled = true;
this._stopButtonEnabled = false;
if (!result.success) {
this.notificationService.notify({
severity: Severity.Error,
message: result.message
@@ -142,8 +174,16 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
}
public onConnectSuccess(params?: INewConnectionParams): void {
this._queryModelService.initializeEdit(this.uri, this.schemaName, this.tableName, this._objectType, this._rowLimit);
this._showTableView.fire(this);
let rowLimit: number = undefined;
let queryString: string = undefined;
if (this._useQueryFilter) {
queryString = this._queryString;
} else {
rowLimit = this._rowLimit;
}
this._queryModelService.initializeEdit(this.uri, this.schemaName, this.tableName, this._objectType, rowLimit, queryString);
this.showResultsEditor();
}
public onDisconnect(): void {
@@ -157,27 +197,19 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
// Boiler Plate Functions
public matches(otherInput: any): boolean {
if (otherInput instanceof EditDataInput) {
return (this.uri === otherInput.uri);
return this._sql.matches(otherInput.sql);
}
return false;
}
public resolve(refresh?: boolean): TPromise<EditorModel> {
return TPromise.as(null);
return this._sql.matches(otherInput);
}
public dispose(): void {
this._queryModelService.disposeQuery(this.uri);
this._sql.dispose();
this._results.dispose();
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;
}
super.dispose();
}
public close(): void {
@@ -186,11 +218,22 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
return this._connectionManagementService.disconnectEditor(this, true);
}).then(() => {
this.dispose();
super.close();
});
}
public get tabColor(): string {
return this._connectionManagementService.getTabColorForUri(this.uri);
}
public get onDidModelChangeContent(): Event<void> { return this._sql.onDidModelChangeContent; }
public get onDidModelChangeEncoding(): Event<void> { return this._sql.onDidModelChangeEncoding; }
public resolve(refresh?: boolean): TPromise<EditorModel> { return this._sql.resolve(); }
public getEncoding(): string { return this._sql.getEncoding(); }
public suggestFileName(): string { return this._sql.suggestFileName(); }
public getName(): string { return this._sql.getName(); }
public get hasAssociatedFilePath(): boolean { return this._sql.hasAssociatedFilePath; }
public setEncoding(encoding: string, mode: EncodingMode /* ignored, we only have Encode */): void {
this._sql.setEncoding(encoding, mode);
}
}

View File

@@ -0,0 +1,105 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { localize } from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import { EditorInput } from 'vs/workbench/common/editor';
/**
* Input for the EditDataResultsEditor. This input helps with logic for the viewing and editing of
* data in the results grid.
*/
export class EditDataResultsInput extends EditorInput {
// Tracks if the editor that holds this input should be visible (i.e. true if a query has been run)
private _visible: boolean;
// Tracks if the editor has holds this input has has bootstrapped angular yet
private _hasBootstrapped: boolean;
// Holds the HTML content for the editor when the editor discards this input and loads another
private _editorContainer: HTMLElement;
public css: HTMLStyleElement;
constructor(private _uri: string) {
super();
this._visible = false;
this._hasBootstrapped = false;
}
getTypeId(): string {
return EditDataResultsInput.ID;
}
matches(other: any): boolean {
if (other instanceof EditDataResultsInput) {
return (other._uri === this._uri);
}
return false;
}
resolve(refresh?: boolean): TPromise<any> {
return TPromise.as(null);
}
supportsSplitEditor(): boolean {
return false;
}
public setBootstrappedTrue(): void {
this._hasBootstrapped = true;
}
public dispose(): void {
this._disposeContainer();
super.dispose();
}
private _disposeContainer() {
if (!this._editorContainer) {
return;
}
let parentContainer = this._editorContainer.parentNode;
if (parentContainer) {
parentContainer.removeChild(this._editorContainer);
this._editorContainer = null;
}
}
//// Properties
static get ID() {
return 'workbench.editorinputs.editDataResultsInput';
}
set container(container: HTMLElement) {
this._disposeContainer();
this._editorContainer = container;
}
get container(): HTMLElement {
return this._editorContainer;
}
get hasBootstrapped(): boolean {
return this._hasBootstrapped;
}
get visible(): boolean {
return this._visible;
}
set visible(visible: boolean) {
this._visible = visible;
}
get uri(): string {
return this._uri;
}
}

View File

@@ -6,13 +6,14 @@
import 'vs/css!sql/parts/query/editor/media/queryEditor';
import { TPromise } from 'vs/base/common/winjs.base';
import * as strings from 'vs/base/common/strings';
import * as DOM from 'vs/base/browser/dom';
import * as nls from 'vs/nls';
import { Builder, Dimension } from 'vs/base/browser/builder';
import { Builder, Dimension, withElementById } from 'vs/base/browser/builder';
import { EditorOptions } from 'vs/workbench/common/editor';
import { EditorOptions, EditorInput } from 'vs/workbench/common/editor';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { Position } from 'vs/platform/editor/common/editor';
import { Position, IEditorControl, IEditor } from 'vs/platform/editor/common/editor';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
@@ -21,6 +22,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { EditDataInput } from 'sql/parts/editData/common/editDataInput';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import * as queryContext from 'sql/parts/query/common/queryContext';
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';
@@ -29,13 +31,22 @@ 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
RefreshTableAction, StopRefreshTableAction, ChangeMaxRowsAction, ChangeMaxRowsActionItem, ShowQueryPaneAction
} 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';
import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor';
import { CodeEditor } from 'vs/editor/browser/codeEditor';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ISelectionData } from 'sqlops';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { IFlexibleSash, VerticalFlexibleSash, HorizontalFlexibleSash } from 'sql/parts/query/views/flexibleSash';
import { Orientation } from 'vs/base/browser/ui/sash/sash';
import { EditDataResultsEditor } from 'sql/parts/editData/editor/editDataResultsEditor';
import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput';
/**
* Editor that hosts an action bar and a resultSetInput for an edit data session
@@ -44,17 +55,34 @@ export class EditDataEditor extends BaseEditor {
public static ID: string = 'workbench.editor.editDataEditor';
// The height of the tabs above the editor
private readonly _tabHeight: number = 35;
// The minimum width/height of the editors hosted in the QueryEditor
private readonly _minEditorSize: number = 220;
private _sash: IFlexibleSash;
private _dimension: Dimension;
private _container: HTMLElement;
private _resultsEditor: EditDataResultsEditor;
private _resultsEditorContainer: HTMLElement;
private _sqlEditor: TextResourceEditor;
private _sqlEditorContainer: HTMLElement;
private _taskbar: Taskbar;
private _taskbarContainer: HTMLElement;
private _changeMaxRowsActionItem: ChangeMaxRowsActionItem;
private _stopRefreshTableAction: StopRefreshTableAction;
private _refreshTableAction: RefreshTableAction;
private _changeMaxRowsAction: ChangeMaxRowsAction;
private _showQueryPaneAction: ShowQueryPaneAction;
private _spinnerElement: HTMLElement;
private _initialized: boolean = false;
private _queryEditorVisible: IContextKey<boolean>;
private hideQueryResultsView = false;
constructor(
@ITelemetryService _telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@@ -62,19 +90,61 @@ export class EditDataEditor extends BaseEditor {
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService,
@IContextMenuService private _contextMenuService: IContextMenuService,
@IQueryModelService private _queryModelService: IQueryModelService,
@IEditorDescriptorService private _editorDescriptorService: IEditorDescriptorService,
@IEditorGroupService private _editorGroupService: IEditorGroupService,
@IContextKeyService contextKeyService: IContextKeyService,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IBootstrapService private _bootstrapService: IBootstrapService
) {
super(EditDataEditor.ID, _telemetryService, themeService);
if (contextKeyService) {
this._queryEditorVisible = queryContext.QueryEditorVisibleContext.bindTo(contextKeyService);
}
}
// 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; }
public get uri(): string { return this.input ? this.editDataInput.uri.toString() : undefined; }
public set resultsEditorVisibility(isVisible: boolean) {
let input: EditDataInput = <EditDataInput>this.input;
input.results.visible = isVisible;
}
/**
* Changes the position of the editor.
*/
public changePosition(position: Position): void {
if (this._resultsEditor) {
this._resultsEditor.changePosition(position);
}
if (this._sqlEditor) {
this._sqlEditor.changePosition(position);
}
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 {
if (this._resultsEditor) {
this._resultsEditor.clearInput();
}
if (this._sqlEditor) {
this._sqlEditor.clearInput();
}
this._disposeEditors();
super.clearInput();
}
public close(): void {
this.editDataInput.close();
}
/**
* Called to create the editor in the parent builder.
@@ -83,9 +153,82 @@ export class EditDataEditor extends BaseEditor {
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);
}
public dispose(): void {
this._disposeEditors();
super.dispose();
}
/**
* Sets focus on this editor. Specifically, it sets the focus on the hosted text editor.
*/
public focus(): void {
if (this._sqlEditor) {
this._sqlEditor.focus();
}
}
public getControl(): IEditorControl {
if (this._sqlEditor) {
return this._sqlEditor.getControl();
}
return null;
}
public getEditorText(): string {
if (this._sqlEditor && this._sqlEditor.getControl()) {
let control = this._sqlEditor.getControl();
let codeEditor: CodeEditor = <CodeEditor>control;
if (codeEditor) {
let value = codeEditor.getModel().getValue();
if (value !== undefined && value.length > 0) {
return value;
}
}
}
return '';
}
/**
* Hide the spinner element to show that something was happening, hidden by default
*/
public hideSpinner(): void {
this._spinnerElement.style.visibility = 'hidden';
}
/**
* 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;
if (this._sash) {
this._setSashDimension();
this._sash.layout();
}
this._doLayout();
this._resizeGridContents();
}
/**
* Sets this editor and the sub-editors to visible.
*/
public setEditorVisible(visible: boolean, position: Position): void {
if (this._resultsEditor) {
this._resultsEditor.setVisible(visible, position);
}
if (this._sqlEditor) {
this._sqlEditor.setVisible(visible, position);
}
super.setEditorVisible(visible, position);
// Note: must update after calling super.setEditorVisible so that the accurate count is handled
this._updateQueryEditorVisible(visible);
}
/**
@@ -95,9 +238,9 @@ export class EditDataEditor extends BaseEditor {
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()));
this._register(newInput.updateTaskbarEvent((owner) => this._updateTaskbar(owner)));
this._register(newInput.editorInitializingEvent((initializing) => this._onEditorInitializingChanged(initializing)));
this._register(newInput.showResultsEditorEvent(() => this._showResultsEditor()));
newInput.onRowDropDownSet(this._changeMaxRowsActionItem.defaultRowCount);
newInput.setupComplete();
}
@@ -106,15 +249,6 @@ export class EditDataEditor extends BaseEditor {
.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
*/
@@ -126,92 +260,74 @@ export class EditDataEditor extends BaseEditor {
}, 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()) {
public toggleResultsEditorVisibility(): void {
let input = <EditDataInput>this.input;
let hideResults = this.hideQueryResultsView;
this.hideQueryResultsView = !this.hideQueryResultsView;
if (!input.results) {
return;
}
this._createTableViewContainer();
this._setTableViewVisible();
this.setInput(this.editDataInput, this.options);
this.resultsEditorVisibility = hideResults;
this._doLayout();
}
// 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 _createEditor(editorInput: EditorInput, container: HTMLElement): TPromise<BaseEditor> {
const descriptor = this._editorDescriptorService.getEditor(editorInput);
if (!descriptor) {
return TPromise.wrapError(new Error(strings.format('Can not find a registered editor for the input {0}', editorInput)));
}
let editor = descriptor.instantiate(this._instantiationService);
editor.create(new Builder(container));
editor.setVisible(this.isVisible(), this.position);
return TPromise.as(editor);
}
/**
* Appends the HTML for the EditDataResultsEditor to the EditDataEditor. If the HTML has not yet been
* created, it creates it and appends it. If it has already been created, it locates it and
* appends it.
*/
private _createResultsEditorContainer() {
this._createSash();
const parentElement = this.getContainer().getHTMLElement();
let input = <EditDataInput>this.input;
if (!input.results.container) {
this._resultsEditorContainer = DOM.append(parentElement, DOM.$('.editDataContainer-horizontal'));
this._resultsEditorContainer.style.position = 'absolute';
input.results.container = this._resultsEditorContainer;
} else {
this._resultsEditorContainer = DOM.append(parentElement, input.results.container);
}
}
/**
* Creates the sash with the requested orientation and registers sash callbacks
*/
private _createSash(): void {
if (!this._sash) {
let parentElement: HTMLElement = this.getContainer().getHTMLElement();
this._sash = this._register(new HorizontalFlexibleSash(parentElement, this._minEditorSize));
this._setSashDimension();
this._register(this._sash.onPositionChange(position => this._doLayout()));
}
this._sash.show();
}
/**
* Appends the HTML for the SQL editor. Creates new HTML every time.
*/
private _createSqlEditorContainer() {
const parentElement = this.getContainer().getHTMLElement();
this._sqlEditorContainer = DOM.append(parentElement, DOM.$('.details-editor-container'));
this._sqlEditorContainer.style.position = 'absolute';
}
private _createTaskbar(parentElement: HTMLElement): void {
@@ -222,22 +338,25 @@ export class EditDataEditor extends BaseEditor {
});
// Create Actions for the toolbar
this._stopRefreshTableAction = this._instantiationService.createInstance(StopRefreshTableAction, this);
this._refreshTableAction = this._instantiationService.createInstance(RefreshTableAction, this);
this._stopRefreshTableAction = this._instantiationService.createInstance(StopRefreshTableAction, this);
this._changeMaxRowsAction = this._instantiationService.createInstance(ChangeMaxRowsAction, this);
this._showQueryPaneAction = this._instantiationService.createInstance(ShowQueryPaneAction, this);
// Create HTML Elements for the taskbar
let separator = Taskbar.createTaskbarSeparator();
let textSeperator = Taskbar.createTaskbarText(nls.localize('maxRowTaskbar', 'Max Rows:'));
let textSeparator = 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 },
{ action: this._stopRefreshTableAction },
{ element: separator },
{ element: textSeperator },
{ element: textSeparator },
{ action: this._changeMaxRowsAction },
{ action: this._showQueryPaneAction },
{ element: this._spinnerElement }
];
this._taskbar.setContent(content);
@@ -258,32 +377,181 @@ export class EditDataEditor extends BaseEditor {
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);
private _disposeEditors(): void {
if (this._sqlEditor) {
this._sqlEditor.dispose();
this._sqlEditor = null;
}
if (this._resultsEditor) {
this._resultsEditor.dispose();
this._resultsEditor = null;
}
this._updateTaskbar(newInput);
return returnValue;
let thisEditorParent: HTMLElement = this.getContainer().getHTMLElement();
if (this._sqlEditorContainer) {
let sqlEditorParent: HTMLElement = this._sqlEditorContainer.parentElement;
if (sqlEditorParent && sqlEditorParent === thisEditorParent) {
this._sqlEditorContainer.parentElement.removeChild(this._sqlEditorContainer);
}
this._sqlEditorContainer = null;
}
if (this._resultsEditorContainer) {
let resultsEditorParent: HTMLElement = this._resultsEditorContainer.parentElement;
if (resultsEditorParent && resultsEditorParent === thisEditorParent) {
this._resultsEditorContainer.parentElement.removeChild(this._resultsEditorContainer);
}
this._resultsEditorContainer = null;
this.hideQueryResultsView = false;
}
}
private _doLayout(skipResizeGridContent: boolean = false): void {
if (!this._isResultsEditorVisible() && this._sqlEditor) {
this._doLayoutSql();
return;
}
if (!this._sqlEditor || !this._resultsEditor || !this._dimension || !this._sash) {
return;
}
this._doLayoutHorizontal();
if (!skipResizeGridContent) {
this._resizeGridContents();
}
}
private _doLayoutHorizontal(): void {
let splitPointTop: number = this._sash.getSplitPoint();
let parent: ClientRect = this.getContainer().getHTMLElement().getBoundingClientRect();
let sqlEditorHeight: number;
let sqlEditorTop: number;
let resultsEditorHeight: number;
let resultsEditorTop: number;
let editorTopOffset = parent.top + this._getTaskBarHeight();
this._resultsEditorContainer.hidden = false;
let titleBar = withElementById('workbench.parts.titlebar');
if (this.queryPaneEnabled()) {
this._sqlEditorContainer.hidden = false;
sqlEditorTop = editorTopOffset;
sqlEditorHeight = splitPointTop - sqlEditorTop;
resultsEditorTop = splitPointTop;
resultsEditorHeight = parent.bottom - resultsEditorTop;
if (titleBar) {
sqlEditorHeight += DOM.getContentHeight(titleBar.getHTMLElement());
}
} else {
this._sqlEditorContainer.hidden = true;
sqlEditorTop = editorTopOffset;
sqlEditorHeight = 0;
resultsEditorTop = editorTopOffset;
resultsEditorHeight = parent.bottom - resultsEditorTop;
if (titleBar) {
resultsEditorHeight += DOM.getContentHeight(titleBar.getHTMLElement());
}
}
this._sqlEditorContainer.style.height = `${sqlEditorHeight}px`;
this._sqlEditorContainer.style.width = `${this._dimension.width}px`;
this._sqlEditorContainer.style.top = `${sqlEditorTop}px`;
this._resultsEditorContainer.style.height = `${resultsEditorHeight}px`;
this._resultsEditorContainer.style.width = `${this._dimension.width}px`;
this._resultsEditorContainer.style.top = `${resultsEditorTop}px`;
this._sqlEditor.layout(new Dimension(this._dimension.width, sqlEditorHeight));
this._resultsEditor.layout(new Dimension(this._dimension.width, resultsEditorHeight));
}
private _doLayoutSql() {
if (this._resultsEditorContainer) {
this._resultsEditorContainer.style.width = '0px';
this._resultsEditorContainer.style.height = '0px';
this._resultsEditorContainer.style.left = '0px';
this._resultsEditorContainer.hidden = true;
}
if (this._dimension) {
let sqlEditorHeight: number;
if (this.queryPaneEnabled()) {
this._sqlEditorContainer.hidden = false;
sqlEditorHeight = this._dimension.height - this._getTaskBarHeight();
} else {
this._sqlEditorContainer.hidden = true;
sqlEditorHeight = 0;
}
this._sqlEditorContainer.style.height = `${sqlEditorHeight}px`;
this._sqlEditorContainer.style.width = `${this._dimension.width}px`;
this._sqlEditor.layout(new Dimension(this._dimension.width, sqlEditorHeight));
}
}
private _getTaskBarHeight(): number {
let taskBarElement = this._taskbar.getContainer().getHTMLElement();
return DOM.getContentHeight(taskBarElement);
}
/**
* Returns true if the results table for the current edit data session is visible
* Public for testing only.
*/
private _isResultsEditorVisible(): boolean {
let input: EditDataInput = <EditDataInput>this.input;
if (!input) {
return false;
}
return input.results.visible;
}
private _onEditorInitializingChanged(initializing: boolean): void {
if (initializing) {
this.showSpinner();
} else {
this._initialized = true;
this.hideSpinner();
}
}
/**
* Sets input for the results editor after it has been created.
*/
private _onResultsEditorCreated(resultsEditor: EditDataResultsEditor, resultsInput: EditDataResultsInput, options: EditorOptions): TPromise<void> {
this._resultsEditor = resultsEditor;
return this._resultsEditor.setInput(resultsInput, options);
}
/**
* Sets input for the SQL editor after it has been created.
*/
private _onSqlEditorCreated(sqlEditor: TextResourceEditor, sqlInput: UntitledEditorInput, options: EditorOptions): TPromise<void> {
this._sqlEditor = sqlEditor;
return this._sqlEditor.setInput(sqlInput, options);
}
private _resizeGridContents(): void {
if (this._isResultsEditorVisible()) {
let queryInput: EditDataInput = <EditDataInput>this.input;
let uri: string = queryInput.uri;
if (uri) {
this._queryModelService.resizeResultsets(uri);
}
}
}
/**
@@ -291,68 +559,177 @@ export class EditDataEditor extends BaseEditor {
* - Opened for the first time
* - Opened with a new EditDataInput
*/
private _setNewInput(newInput: EditDataInput, options?: EditorOptions): TPromise<void> {
private _setNewInput(newInput: EditDataInput, options?: EditorOptions): TPromise<any> {
// Promises that will ensure proper ordering of editor creation logic
let createEditors: () => TPromise<any>;
let onEditorsCreated: (result) => TPromise<any>;
// If both editors exist, create joined promises - one for each editor
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
createEditors = () => {
return TPromise.join([
this._createEditor(<EditDataResultsInput>newInput.results, this._resultsEditorContainer),
this._createEditor(<UntitledEditorInput>newInput.sql, this._sqlEditorContainer)
]);
};
onEditorsCreated = (result: IEditor[]) => {
return TPromise.join([
this._onResultsEditorCreated(<EditDataResultsEditor>result[0], newInput.results, options),
this._onSqlEditorCreated(<TextResourceEditor>result[1], newInput.sql, options)
]);
};
// If only the sql editor exists, create a promise and wait for the sql editor to be created
} else {
createEditors = () => {
return this._createEditor(<UntitledEditorInput>newInput.sql, this._sqlEditorContainer);
};
onEditorsCreated = (result: TextResourceEditor) => {
return this._onSqlEditorCreated(result, newInput.sql, options);
};
this._bootstrapService.bootstrap(
EditDataModule,
parent,
EDITDATA_SELECTOR,
params,
this.editDataInput);
}
return TPromise.wrap<void>(null);
// Create a promise to re render the layout after the editor creation logic
let doLayout: () => TPromise<any> = () => {
this._doLayout();
return TPromise.as(undefined);
};
// Run all three steps synchronously
return createEditors()
.then(onEditorsCreated)
.then(doLayout);
}
private _setTableViewVisible(): void {
this.editDataInput.setVisibleTrue();
private _setSashDimension(): void {
if (!this._dimension) {
return;
}
this._sash.setDimenesion(this._dimension);
}
/**
* Makes visible the results table for the current edit data session
*/
private _showResultsEditor(): void {
if (this._isResultsEditorVisible()) {
return;
}
this._editorGroupService.pinEditor(this.position, this.input);
let input = <EditDataInput>this.input;
this._createResultsEditorContainer();
this._createEditor(<EditDataResultsInput>input.results, this._resultsEditorContainer)
.then(result => {
this._onResultsEditorCreated(<EditDataResultsEditor>result, input.results, this.options);
this.resultsEditorVisibility = true;
this.hideQueryResultsView = false;
this._doLayout(true);
});
}
/**
* 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> {
if (this._sqlEditor) {
this._sqlEditor.clearInput();
}
if (oldInput) {
this._disposeEditors();
}
this._createSqlEditorContainer();
if (this._isResultsEditorVisible()) {
this._createResultsEditorContainer();
let uri: string = newInput.uri;
if (uri) {
this._queryModelService.refreshResultsets(uri);
}
}
if (this._sash) {
if (this._isResultsEditorVisible()) {
this._sash.show();
} else {
this._sash.hide();
}
}
this._updateTaskbar(newInput);
return this._setNewInput(newInput, options);
}
private _updateQueryEditorVisible(currentEditorIsVisible: boolean): void {
if (this._queryEditorVisible) {
let visible = currentEditorIsVisible;
if (!currentEditorIsVisible) {
// Current editor is closing but still tracked as visible. Check if any other editor is visible
const candidates = [...this._editorService.getVisibleEditors()].filter(e => {
if (e && e.getId) {
return e.getId() === EditDataEditor.ID;
}
return false;
});
// Note: require 2 or more candidates since current is closing but still
// counted as visible
visible = candidates.length > 1;
}
this._queryEditorVisible.set(visible);
}
}
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;
this._showQueryPaneAction.queryPaneEnabled = owner.queryPaneEnabled;
}
}
/**
* Calls the run method of this editor's RunQueryAction
*/
public runQuery(): void {
this._refreshTableAction.run();
}
/**
* Calls the run method of this editor's CancelQueryAction
*/
public cancelQuery(): void {
this._stopRefreshTableAction.run();
}
public toggleQueryPane(): void {
this.editDataInput.queryPaneEnabled = !this.queryPaneEnabled();
if (this.queryPaneEnabled()) {
this._showQueryEditor();
} else {
this._hideQueryEditor();
}
this._doLayout(false);
}
private _showQueryEditor(): void {
this._sqlEditorContainer.hidden = false;
this._changeMaxRowsActionItem.disable();
}
private _hideQueryEditor(): void {
this._sqlEditorContainer.hidden = true;
this._changeMaxRowsActionItem.enable();
}
public queryPaneEnabled(): boolean {
return this.editDataInput.queryPaneEnabled;
}
}

View File

@@ -0,0 +1,113 @@
import { Dimension, Builder } from 'vs/base/browser/builder';
import { EditorOptions } from 'vs/workbench/common/editor';
import { TPromise } from 'vs/base/common/winjs.base';
import { getZoomLevel } from 'vs/base/browser/browser';
import { Configuration } from 'vs/editor/browser/config/configuration';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
import { IBootstrapService } from 'sql/services/bootstrap/bootstrapService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { BareResultsGridInfo } from 'sql/parts/query/editor/queryResultsEditor';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import * as dom from 'vs/base/browser/dom';
import * as types from 'vs/base/common/types';
import { EditDataComponentParams } from 'sql/services/bootstrap/bootstrapParams';
import { EditDataModule } from 'sql/parts/grid/views/editData/editData.module';
import { EDITDATA_SELECTOR } from 'sql/parts/grid/views/editData/editData.component';
import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput';
export class EditDataResultsEditor extends BaseEditor {
public static ID: string = 'workbench.editor.editDataResultsEditor';
public static AngularSelectorString: string = 'slickgrid-container.slickgridContainer';
protected _input: EditDataResultsInput;
protected _rawOptions: BareResultsGridInfo;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@IQueryModelService private _queryModelService: IQueryModelService,
@IBootstrapService private _bootstrapService: IBootstrapService,
@IConfigurationService private _configurationService: IConfigurationService
) {
super(EditDataResultsEditor.ID, telemetryService, themeService);
this._rawOptions = BareResultsGridInfo.createFromRawSettings(this._configurationService.getValue('resultsGrid'), getZoomLevel());
this._configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('resultsGrid')) {
this._rawOptions = BareResultsGridInfo.createFromRawSettings(this._configurationService.getValue('resultsGrid'), getZoomLevel());
this._applySettings();
}
});
}
public get input(): EditDataResultsInput {
return this._input;
}
public createEditor(parent: Builder): void {
}
public dispose(): void {
super.dispose();
}
public layout(dimension: Dimension): void {
}
public setInput(input: EditDataResultsInput, options: EditorOptions): TPromise<void> {
super.setInput(input, options);
this._applySettings();
if (!input.hasBootstrapped) {
this._bootstrapAngular();
}
return TPromise.wrap<void>(null);
}
private _applySettings() {
if (this.input && this.input.container) {
Configuration.applyFontInfoSlow(this.getContainer().getHTMLElement(), this._rawOptions);
if (!this.input.css) {
this.input.css = dom.createStyleSheet(this.input.container);
}
let cssRuleText = '';
if (types.isNumber(this._rawOptions.cellPadding)) {
cssRuleText = this._rawOptions.cellPadding + 'px';
} else {
cssRuleText = this._rawOptions.cellPadding.join('px ') + 'px;';
}
let content = `.grid .slick-cell { padding: ${cssRuleText}; }`;
this.input.css.innerHTML = content;
}
}
/**
* Load the angular components and record for this input that we have done so
*/
private _bootstrapAngular(): void {
let input = <EditDataResultsInput>this.input;
let uri = input.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 = input.container;
let params: EditDataComponentParams = { dataService: dataService };
this._bootstrapService.bootstrap(
EditDataModule,
parent,
EDITDATA_SELECTOR,
params,
input);
}
}

View File

@@ -7,7 +7,7 @@ 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 { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
import { EventEmitter } from 'sql/base/common/eventEmitter';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import { EditDataEditor } from 'sql/parts/editData/editor/editDataEditor';
@@ -63,7 +63,7 @@ export abstract class EditDataAction extends Action {
* Action class that refreshes the table for an edit data session
*/
export class RefreshTableAction extends EditDataAction {
private static EnabledClass = 'refresh';
private static EnabledClass = 'start';
public static ID = 'refreshTableAction';
constructor(editor: EditDataEditor,
@@ -72,14 +72,24 @@ export class RefreshTableAction extends EditDataAction {
@INotificationService private _notificationService: INotificationService,
) {
super(editor, RefreshTableAction.ID, RefreshTableAction.EnabledClass, _connectionManagementService);
this.label = nls.localize('editData.refresh', 'Refresh');
this.label = nls.localize('editData.run', 'Run');
}
public run(): TPromise<void> {
if (this.isConnected(this.editor)) {
let input = this.editor.editDataInput;
let rowLimit: number = undefined;
let queryString: string = undefined;
if (input.queryPaneEnabled) {
queryString = input.queryString = this.editor.getEditorText();
} else {
rowLimit = input.rowLimit;
}
this._queryModelService.disposeEdit(input.uri).then((result) => {
this._queryModelService.initializeEdit(input.uri, input.schemaName, input.tableName, input.objectType, input.rowLimit);
this._queryModelService.initializeEdit(input.uri, input.schemaName, input.tableName, input.objectType, rowLimit, queryString);
input.showResultsEditor();
}, error => {
this._notificationService.notify({
severity: Severity.Error,
@@ -162,10 +172,12 @@ export class ChangeMaxRowsActionItem extends EventEmitter implements IActionItem
this._options = ['200', '1000', '10000'];
this._currentOptionsIndex = 0;
this.toDispose = [];
this.selectBox = new SelectBox([], -1, contextViewService);
this.selectBox = new SelectBox(this._options, this._options[this._currentOptionsIndex], contextViewService);
this._registerListeners();
this._refreshOptions();
this.defaultRowCount = Number(this._options[this._currentOptionsIndex]);
this.toDispose.push(attachSelectBoxStyler(this.selectBox, _themeService));
}
public render(container: HTMLElement): void {
@@ -181,6 +193,14 @@ export class ChangeMaxRowsActionItem extends EventEmitter implements IActionItem
return true;
}
public enable(): void {
this.selectBox.enable();
}
public disable(): void {
this.selectBox.disable();
}
public set setCurrentOptionIndex(selection: number) {
this._currentOptionsIndex = this._options.findIndex(x => x === selection.toString());
this._refreshOptions();
@@ -210,3 +230,40 @@ export class ChangeMaxRowsActionItem extends EventEmitter implements IActionItem
this.toDispose.push(attachSelectBoxStyler(this.selectBox, this._themeService));
}
}
/**
* Action class that is tied with toggling the Query editor
*/
export class ShowQueryPaneAction extends EditDataAction {
private static EnabledClass = 'filterLabel';
public static ID = 'showQueryPaneAction';
private readonly showSqlLabel = nls.localize('editData.showSql', 'Show SQL Pane');
private readonly closeSqlLabel = nls.localize('editData.closeSql', 'Close SQL Pane');
constructor(editor: EditDataEditor,
@IQueryModelService private _queryModelService: IQueryModelService,
@IConnectionManagementService _connectionManagementService: IConnectionManagementService
) {
super(editor, ShowQueryPaneAction.ID, ShowQueryPaneAction.EnabledClass, _connectionManagementService);
this.label = this.showSqlLabel;
}
public set queryPaneEnabled(value: boolean) {
this.updateLabel(value);
}
private updateLabel(queryPaneEnabled: boolean): void {
if (queryPaneEnabled) {
this.label = this.closeSqlLabel;
} else {
this.label = this.showSqlLabel;
}
}
public run(): TPromise<void> {
this.editor.toggleQueryPane();
this.updateLabel(this.editor.queryPaneEnabled());
return TPromise.as(null);
}
}

View File

@@ -22,6 +22,8 @@ import { GridParentComponent } from 'sql/parts/grid/views/gridParentComponent';
import { EditDataGridActionProvider } from 'sql/parts/grid/views/editData/editDataGridActions';
import { error } from 'sql/base/common/log';
import { clone } from 'sql/base/common/objects';
import { INotificationService } from 'vs/platform/notification/common/notification';
import Severity from 'vs/base/common/severity';
export const EDITDATA_SELECTOR: string = 'editdata-component';
@@ -40,7 +42,6 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
// All datasets
private dataSet: IGridDataSet;
private scrollTimeOut: number;
private messagesAdded = false;
private scrollEnabled = true;
private firstRender = true;
private totalElapsedTimeSpan: number;
@@ -54,6 +55,8 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
private removingNewRow: boolean;
private rowIdMappings: { [gridRowId: number]: number } = {};
private notificationService: INotificationService;
// Edit Data functions
public onActiveCellChanged: (event: { row: number, column: number }) => void;
public onCellEditEnd: (event: { row: number, column: number, newValue: any }) => void;
@@ -75,6 +78,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
let editDataParameters: EditDataComponentParams = this._bootstrapService.getBootstrapParams(this._el.nativeElement.tagName);
this.dataService = editDataParameters.dataService;
this.actionProvider = this._bootstrapService.instantiationService.createInstance(EditDataGridActionProvider, this.dataService, this.onGridSelectAll(), this.onDeleteRow(), this.onRevertRow());
this.notificationService = bootstrapService.notificationService;
}
/**
@@ -127,7 +131,6 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
self.renderedDataSets = self.placeHolderDataSets;
self.totalElapsedTimeSpan = undefined;
self.complete = false;
self.messagesAdded = false;
// Hooking up edit functions
this.onIsCellEditValid = (row, column, value): boolean => {
@@ -302,7 +305,6 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
handleComplete(self: EditDataComponent, event: any): void {
self.totalElapsedTimeSpan = event.data;
self.complete = true;
self.messagesAdded = true;
}
handleEditSessionReady(self, event): void {
@@ -310,7 +312,12 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
}
handleMessage(self: EditDataComponent, event: any): void {
// TODO: what do we do with messages?
if (event.data && event.data.isError) {
self.notificationService.notify({
severity: Severity.Error,
message: event.data.message
});
}
}
handleResultSet(self: EditDataComponent, event: any): void {
@@ -360,7 +367,6 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
undefinedDataSet.dataRows = undefined;
undefinedDataSet.resized = new EventEmitter();
self.placeHolderDataSets.push(undefinedDataSet);
self.messagesAdded = true;
self.onScroll(0);
// Setup the state of the selected cell

View File

@@ -192,9 +192,10 @@ export class OEEditDataAction extends EditDataAction {
id: string, label: string,
@IQueryEditorService protected _queryEditorService: IQueryEditorService,
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
@IScriptingService protected _scriptingService: IScriptingService,
@IInstantiationService private _instantiationService: IInstantiationService
) {
super(id, label, _queryEditorService, _connectionManagementService);
super(id, label, _queryEditorService, _connectionManagementService, _scriptingService);
}
public run(actionContext: any): TPromise<boolean> {

View File

@@ -33,6 +33,8 @@ import { QueryPlanEditor } from 'sql/parts/queryPlan/queryPlanEditor';
import { QueryPlanInput } from 'sql/parts/queryPlan/queryPlanInput';
import * as Constants from 'sql/parts/query/common/constants';
import { localize } from 'vs/nls';
import { EditDataResultsEditor } from 'sql/parts/editData/editor/editDataResultsEditor';
import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput';
const gridCommandsWeightBonus = 100; // give our commands a little bit more weight over other default list/tree commands
@@ -81,6 +83,16 @@ const editDataEditorDescriptor = new EditorDescriptor(
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(editDataEditorDescriptor, [new SyncDescriptor(EditDataInput)]);
// Editor
const editDataResultsEditorDescriptor = new EditorDescriptor(
EditDataResultsEditor,
EditDataResultsEditor.ID,
'EditDataResults'
);
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(editDataResultsEditorDescriptor, [new SyncDescriptor(EditDataResultsInput)]);
let actionRegistry = <IWorkbenchActionRegistry>Registry.as(Extensions.WorkbenchActions);
// Query Actions

View File

@@ -30,7 +30,7 @@ export interface IQueryEditorService {
newQueryPlanEditor(xmlShowPlan: string): Promise<any>;
// Creates new edit data session
newEditDataEditor(schemaName: string, tableName: string): Promise<IConnectableInput>;
newEditDataEditor(schemaName: string, tableName: string, queryString: string): Promise<IConnectableInput>;
// Clears any QueryEditor data for the given URI held by this service
onQueryInputClosed(uri: string): void;

View File

@@ -43,7 +43,7 @@ export interface IQueryManagementService {
onEditSessionReady(ownerUri: string, success: boolean, message: string): void;
// Edit Data Functions
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable<void>;
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Thenable<void>;
disposeEdit(ownerUri: string): Thenable<void>;
updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable<sqlops.EditUpdateCellResult>;
commitEdit(ownerUri): Thenable<void>;
@@ -68,7 +68,7 @@ export interface IQueryRequestHandler {
saveResults(requestParams: sqlops.SaveResultsRequestParams): Thenable<sqlops.SaveResultRequestResult>;
// Edit Data actions
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable<void>;
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Thenable<void>;
disposeEdit(ownerUri: string): Thenable<void>;
updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable<sqlops.EditUpdateCellResult>;
commitEdit(ownerUri): Thenable<void>;
@@ -244,9 +244,9 @@ export class QueryManagementService implements IQueryManagementService {
}
// Edit Data Functions
public initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable<void> {
public initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Thenable<void> {
return this._runAction(ownerUri, (runner) => {
return runner.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit);
return runner.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit, queryString);
});
}

View File

@@ -19,6 +19,7 @@ import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
import * as Constants from 'sql/parts/query/common/constants';
import * as ConnectionConstants from 'sql/parts/connection/common/constants';
import { EditDataEditor } from 'sql/parts/editData/editor/editDataEditor';
const singleQuote = '\'';
@@ -98,8 +99,8 @@ export class RunQueryKeyboardAction extends Action {
public run(): TPromise<void> {
let editor = this._editorService.getActiveEditor();
if (editor && editor instanceof QueryEditor) {
let queryEditor: QueryEditor = editor;
if (editor && (editor instanceof QueryEditor || editor instanceof EditDataEditor)) {
let queryEditor: QueryEditor | EditDataEditor = editor;
queryEditor.runQuery();
}
return TPromise.as(null);
@@ -174,8 +175,8 @@ export class CancelQueryKeyboardAction extends Action {
public run(): TPromise<void> {
let editor = this._editorService.getActiveEditor();
if (editor && editor instanceof QueryEditor) {
let queryEditor: QueryEditor = editor;
if (editor && (editor instanceof QueryEditor || editor instanceof EditDataEditor)) {
let queryEditor: QueryEditor | EditDataEditor = editor;
queryEditor.cancelQuery();
}
return TPromise.as(null);

View File

@@ -57,7 +57,7 @@ export interface IQueryModelService {
// Edit Data Functions
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): void;
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): void;
disposeEdit(ownerUri: string): Thenable<void>;
updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable<EditUpdateCellResult>;
commitEdit(ownerUri): Thenable<void>;

View File

@@ -352,7 +352,7 @@ export class QueryModelService implements IQueryModelService {
}
// EDIT DATA METHODS /////////////////////////////////////////////////////
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): void {
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): void {
// Reuse existing query runner if it exists
let queryRunner: QueryRunner;
let info: QueryInfo;
@@ -368,6 +368,8 @@ export class QueryModelService implements IQueryModelService {
queryRunner = existingRunner;
} else {
info = new QueryInfo();
// We do not have a query runner for this editor, so create a new one
// and map it to the results uri
queryRunner = this._instantiationService.createInstance(QueryRunner, ownerUri, ownerUri);
@@ -376,15 +378,21 @@ export class QueryModelService implements IQueryModelService {
});
queryRunner.addListener(QREvents.BATCH_START, batch => {
let link = undefined;
let messageText = LocalizedConstants.runQueryBatchStartMessage;
if (batch.selection) {
link = {
text: strings.format(LocalizedConstants.runQueryBatchStartLine, batch.selection.startLine + 1),
uri: ''
};
if (info.selectionSnippet) {
// This indicates it's a query string. Do not include line information since it'll be inaccurate, but show some of the
// executed query text
messageText = nls.localize('runQueryStringBatchStartMessage', 'Started executing query "{0}"', info.selectionSnippet);
} else {
link = {
text: strings.format(LocalizedConstants.runQueryBatchStartLine, batch.selection.startLine + 1)
};
}
}
let message = {
message: LocalizedConstants.runQueryBatchStartMessage,
batchId: undefined,
message: messageText,
batchId: batch.id,
isError: false,
time: new Date().toLocaleTimeString(),
link: link
@@ -407,13 +415,20 @@ export class QueryModelService implements IQueryModelService {
this._fireQueryEvent(e.ownerUri, 'editSessionReady');
});
info = new QueryInfo();
info.queryRunner = queryRunner;
info.dataService = this._instantiationService.createInstance(DataService, ownerUri);
this._queryInfoMap.set(ownerUri, info);
}
queryRunner.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit);
if (queryString) {
if (queryString.length < selectionSnippetMaxLen) {
info.selectionSnippet = queryString;
} else {
info.selectionSnippet = queryString.substring(0, selectionSnippetMaxLen - 3) + '...';
}
}
queryRunner.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit, queryString);
}
public cancelInitializeEdit(input: QueryRunner | string): void {

View File

@@ -300,13 +300,13 @@ export default class QueryRunner {
/*
* Handle a session ready event for Edit Data
*/
public initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable<void> {
public initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Thenable<void> {
// Update internal state to show that we're executing the query
this._isExecuting = true;
this._totalElapsedMilliseconds = 0;
// TODO issue #228 add statusview callbacks here
return this._queryManagementService.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit).then(result => {
return this._queryManagementService.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit, queryString).then(result => {
// The query has started, so lets fire up the result pane
this._eventEmitter.emit(EventType.START);
this._queryManagementService.registerRunner(this, ownerUri);

View File

@@ -29,6 +29,7 @@ import paths = require('vs/base/common/paths');
import { isLinux } from 'vs/base/common/platform';
import { Schemas } from 'vs/base/common/network';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput';
const fs = require('fs');
@@ -120,7 +121,7 @@ export class QueryEditorService implements IQueryEditorService {
/**
* Creates new edit data session
*/
public newEditDataEditor(schemaName: string, tableName: string): Promise<IConnectableInput> {
public newEditDataEditor(schemaName: string, tableName: string, sqlContent: string): Promise<IConnectableInput> {
return new Promise<IConnectableInput>((resolve, reject) => {
try {
@@ -129,8 +130,17 @@ export class QueryEditorService implements IQueryEditorService {
let filePath = this.createEditDataFileName(objectName);
let docUri: URI = URI.from({ scheme: Schemas.untitled, path: filePath });
// Create a sql document pane with accoutrements
const fileInput = this._untitledEditorService.createOrGet(docUri, 'sql');
fileInput.resolve().then(m => {
if (sqlContent) {
m.textEditorModel.setValue(sqlContent);
}
});
// Create an EditDataInput for editing
let editDataInput: EditDataInput = this._instantiationService.createInstance(EditDataInput, docUri, schemaName, tableName);
const resultsInput: EditDataResultsInput = this._instantiationService.createInstance(EditDataResultsInput, docUri.toString());
let editDataInput: EditDataInput = this._instantiationService.createInstance(EditDataInput, docUri, schemaName, tableName, fileInput, sqlContent, resultsInput);
this._editorService.openEditor(editDataInput, { pinned: true })
.then((editor) => {
@@ -212,7 +222,7 @@ export class QueryEditorService implements IQueryEditorService {
}
let uri: URI = QueryEditorService._getEditorChangeUri(editor.input, changingToSql);
if(uri.scheme === Schemas.untitled && editor.input instanceof QueryInput)
if(uri.scheme === Schemas.untitled && (editor.input instanceof QueryInput || editor.input instanceof EditDataInput))
{
QueryEditorService.notificationService.notify({
severity: Severity.Error,
@@ -299,10 +309,8 @@ export class QueryEditorService implements IQueryEditorService {
filePath = editDataFileName(counter);
}
// TODO: check if this document name already exists in any open documents tabs
let fileNames: string[] = [];
this._editorGroupService.getStacksModel().groups.map(group => group.getEditors().map(editor => fileNames.push(editor.getName())));
while (fileNames.find(x => x.toUpperCase() === filePath.toUpperCase())) {
let untitledEditors = this._untitledEditorService.getAll();
while (untitledEditors.find(x => x.getName().toUpperCase() === filePath.toUpperCase())) {
counter++;
filePath = editDataFileName(counter);
}

3
src/sql/sqlops.d.ts vendored
View File

@@ -651,7 +651,7 @@ declare module 'sqlops' {
createRow(ownerUri: string): Thenable<EditCreateRowResult>;
deleteRow(ownerUri: string, rowId: number): Thenable<void>;
disposeEdit(ownerUri: string): Thenable<void>;
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable<void>;
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Thenable<void>;
revertCell(ownerUri: string, rowId: number, columnId: number): Thenable<EditRevertCellResult>;
revertRow(ownerUri: string, rowId: number): Thenable<void>;
updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable<EditUpdateCellResult>;
@@ -879,6 +879,7 @@ declare module 'sqlops' {
objectName: string;
schemaName: string;
objectType: string;
queryString: string;
}

View File

@@ -247,8 +247,8 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
return this._resolveProvider<sqlops.QueryProvider>(handle).disposeEdit(ownerUri);
}
$initializeEdit(handle: number, ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable<void> {
return this._resolveProvider<sqlops.QueryProvider>(handle).initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit);
$initializeEdit(handle: number, ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Thenable<void> {
return this._resolveProvider<sqlops.QueryProvider>(handle).initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit, queryString);
}
$revertCell(handle: number, ownerUri: string, rowId: number, columnId: number): Thenable<sqlops.EditRevertCellResult> {

View File

@@ -132,8 +132,8 @@ export class MainThreadDataProtocol implements MainThreadDataProtocolShape {
return self._serializationService.saveAs(requestParams.resultFormat, requestParams.filePath, undefined, true);
}
},
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable<void> {
return self._proxy.$initializeEdit(handle, ownerUri, schemaName, objectName, objectType, rowLimit);
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Thenable<void> {
return self._proxy.$initializeEdit(handle, ownerUri, schemaName, objectName, objectType, rowLimit, queryString);
},
updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable<sqlops.EditUpdateCellResult> {
return self._proxy.$updateCell(handle, ownerUri, rowId, columnId, newValue);

View File

@@ -196,7 +196,7 @@ export abstract class ExtHostDataProtocolShape {
/**
* Initializes a new edit data session for the requested table/view
*/
$initializeEdit(handle: number, ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable<void> { throw ni(); }
$initializeEdit(handle: number, ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Thenable<void> { throw ni(); }
/**
* Reverts any pending changes for the requested cell and returns the original value

View File

@@ -187,19 +187,20 @@ export class EditDataAction extends Action {
constructor(
id: string, label: string,
@IQueryEditorService protected _queryEditorService: IQueryEditorService,
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
@IScriptingService protected _scriptingService: IScriptingService
) {
super(id, label);
}
public run(actionContext: BaseActionContext): TPromise<boolean> {
return new TPromise<boolean>((resolve, reject) => {
TaskUtilities.editData(
TaskUtilities.scriptEditSelect(
actionContext.profile,
actionContext.object.name,
actionContext.object.schema,
actionContext.object,
this._connectionManagementService,
this._queryEditorService
this._queryEditorService,
this._scriptingService
).then(
result => {
resolve(true);

View File

@@ -169,19 +169,33 @@ export function scriptSelect(connectionProfile: IConnectionProfile, metadata: sq
/**
* Opens a new Edit Data session
*/
export function editData(connectionProfile: IConnectionProfile, tableName: string, schemaName: string, connectionService: IConnectionManagementService, queryEditorService: IQueryEditorService): Promise<void> {
return new Promise<void>((resolve) => {
queryEditorService.newEditDataEditor(schemaName, tableName).then((owner: EditDataInput) => {
// Connect our editor
let options: IConnectionCompletionOptions = {
params: { connectionType: ConnectionType.editor, runQueryOnCompletion: RunQueryOnConnectionMode.none, input: owner },
saveTheConnection: false,
showDashboard: false,
showConnectionDialogOnError: true,
showFirewallRuleOnError: true
};
connectionService.connect(connectionProfile, owner.uri, options).then(() => {
resolve();
export function scriptEditSelect(connectionProfile: IConnectionProfile, metadata: sqlops.ObjectMetadata, connectionService: IConnectionManagementService, queryEditorService: IQueryEditorService, scriptingService: IScriptingService): Promise<void> {
return new Promise<void>((resolve, reject) => {
connectionService.connectIfNotConnected(connectionProfile).then(connectionResult => {
let paramDetails: sqlops.ScriptingParamDetails = getScriptingParamDetails(connectionService, connectionResult, metadata);
scriptingService.script(connectionResult, metadata, ScriptOperation.Select, paramDetails).then(result => {
if (result.script) {
queryEditorService.newEditDataEditor(metadata.schema, metadata.name, result.script).then((owner: EditDataInput) => {
// Connect our editor
let options: IConnectionCompletionOptions = {
params: { connectionType: ConnectionType.editor, runQueryOnCompletion: RunQueryOnConnectionMode.none, input: owner },
saveTheConnection: false,
showDashboard: false,
showConnectionDialogOnError: true,
showFirewallRuleOnError: true
};
connectionService.connect(connectionProfile, owner.uri, options).then(() => {
resolve();
});
}).catch(editorError => {
reject(editorError);
});
} else {
let errMsg: string = nls.localize('scriptSelectNotFound', 'No script was returned when calling select script on object ');
reject(errMsg.concat(metadata.metadataTypeName));
}
}, scriptError => {
reject(scriptError);
});
});
});