mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-26 09:35:38 -05:00
Add query execution plan extensibility APIs (#4072)
* WIP 1 * WIP 2 * Fix typos * Iterate on API a bit * Query Tab WIP * More dynamic query tab impl * Fix merge breaks * Update interfaces * Update to single event handler for query events * Remove query plan extension * Add generated JS file
This commit is contained in:
34
src/sql/azdata.proposed.d.ts
vendored
34
src/sql/azdata.proposed.d.ts
vendored
@@ -3576,6 +3576,31 @@ declare module 'azdata' {
|
||||
* Namespace for interacting with query editor
|
||||
*/
|
||||
export namespace queryeditor {
|
||||
export type QueryEvent =
|
||||
| 'queryStart'
|
||||
| 'queryStop'
|
||||
| 'executionPlan';
|
||||
|
||||
export interface QueryEventListener {
|
||||
onQueryEvent(type: QueryEvent, document: queryeditor.QueryDocument, args: any);
|
||||
}
|
||||
|
||||
// new extensibility interfaces
|
||||
export interface QueryDocument {
|
||||
providerId: string;
|
||||
|
||||
uri: string;
|
||||
|
||||
// get the document's execution options
|
||||
getOptions(): Map<string, string>;
|
||||
|
||||
// set the document's execution options
|
||||
setOptions(options: Map<string, string>): void;
|
||||
|
||||
// tab content is build using the modelview UI builder APIs
|
||||
// probably should rename DialogTab class since it is useful outside dialogs
|
||||
createQueryTab(tab: window.DialogTab): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make connection for the query editor
|
||||
@@ -3588,7 +3613,14 @@ declare module 'azdata' {
|
||||
* Run query if it is a query editor and it is already opened.
|
||||
* @param {string} fileUri file URI for the query editor
|
||||
*/
|
||||
export function runQuery(fileUri: string): void;
|
||||
export function runQuery(fileUri: string, options?: Map<string, string>): void;
|
||||
|
||||
/**
|
||||
* Register a query event listener
|
||||
*/
|
||||
export function registerQueryEventListener(listener: queryeditor.QueryEventListener): void;
|
||||
|
||||
export function getQueryDocument(fileUri: string): queryeditor.QueryDocument
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -968,4 +968,8 @@ export class QueryEditor extends BaseEditor {
|
||||
public get changeConnectionAction(): ConnectDatabaseAction {
|
||||
return this._changeConnectionAction;
|
||||
}
|
||||
|
||||
public registerQueryModelViewTab(title: string, componentId: string): void {
|
||||
this._resultsEditor.registerQueryModelViewTab(title, componentId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,4 +165,8 @@ export class QueryResultsEditor extends BaseEditor {
|
||||
public showQueryPlan(xml: string) {
|
||||
this.resultsView.showPlan(xml);
|
||||
}
|
||||
|
||||
public registerQueryModelViewTab(title: string, componentId: string): void {
|
||||
this.resultsView.registerQueryModelViewTab(title, componentId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { GridPanel } from './gridPanel';
|
||||
import { ChartTab } from './charting/chartTab';
|
||||
import { QueryPlanTab } from 'sql/parts/queryPlan/queryPlan';
|
||||
import { TopOperationsTab } from 'sql/parts/queryPlan/topOperations';
|
||||
import { QueryModelViewTab } from 'sql/parts/query/modelViewTab/queryModelViewTab';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { PanelViewlet } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||
@@ -175,12 +176,13 @@ export class QueryResultsView extends Disposable {
|
||||
private chartTab: ChartTab;
|
||||
private qpTab: QueryPlanTab;
|
||||
private topOperationsTab: TopOperationsTab;
|
||||
private dynamicModelViewTabs: QueryModelViewTab[] = [];
|
||||
|
||||
private runnerDisposables: IDisposable[];
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IQueryModelService private queryModelService: IQueryModelService
|
||||
) {
|
||||
super();
|
||||
@@ -207,6 +209,7 @@ export class QueryResultsView extends Disposable {
|
||||
this.runnerDisposables.push(runner.onQueryStart(e => {
|
||||
this.hideChart();
|
||||
this.hidePlan();
|
||||
this.hideDynamicViewModelTabs();
|
||||
this.input.state.visibleTabs = new Set();
|
||||
this.input.state.activeTab = this.resultsTab.identifier;
|
||||
}));
|
||||
@@ -225,6 +228,23 @@ export class QueryResultsView extends Disposable {
|
||||
this._panelView.pushTab(this.topOperationsTab);
|
||||
}
|
||||
}
|
||||
|
||||
// restore query model view tabs
|
||||
this.input.state.visibleTabs.forEach(tabId => {
|
||||
if (tabId.startsWith('querymodelview;')) {
|
||||
// tab id format is 'tab type;title;model view id'
|
||||
let parts = tabId.split(';');
|
||||
if (parts.length === 3) {
|
||||
let tab = this._register(new QueryModelViewTab(parts[1], this.instantiationService));
|
||||
tab.view._componentId = parts[2];
|
||||
this.dynamicModelViewTabs.push(tab);
|
||||
if (!this._panelView.contains(tab)) {
|
||||
this._panelView.pushTab(tab);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.runnerDisposables.push(runner.onQueryEnd(() => {
|
||||
if (runner.isQueryPlan) {
|
||||
runner.planXml.then(e => {
|
||||
@@ -311,10 +331,35 @@ export class QueryResultsView extends Disposable {
|
||||
if (this._panelView.contains(this.qpTab)) {
|
||||
this._panelView.removeTab(this.qpTab.identifier);
|
||||
}
|
||||
|
||||
if (this._panelView.contains(this.topOperationsTab)) {
|
||||
this._panelView.removeTab(this.topOperationsTab.identifier);
|
||||
}
|
||||
}
|
||||
|
||||
public hideDynamicViewModelTabs() {
|
||||
this.dynamicModelViewTabs.forEach(tab => {
|
||||
if (this._panelView.contains(tab)) {
|
||||
this._panelView.removeTab(tab.identifier);
|
||||
}
|
||||
});
|
||||
|
||||
this.dynamicModelViewTabs = [];
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
dispose(this.runnerDisposables);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public registerQueryModelViewTab(title: string, componentId: string): void {
|
||||
let tab = this._register(new QueryModelViewTab(title, this.instantiationService));
|
||||
tab.view._componentId = componentId;
|
||||
this.dynamicModelViewTabs.push(tab);
|
||||
|
||||
this.input.state.visibleTabs.add('querymodelview;' + title + ';' + componentId);
|
||||
if (!this._panelView.contains(tab)) {
|
||||
this._panelView.pushTab(tab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
79
src/sql/parts/query/modelViewTab/media/dialogModal.css
Normal file
79
src/sql/parts/query/modelViewTab/media/dialogModal.css
Normal file
@@ -0,0 +1,79 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.dialogModal-body {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
min-width: 500px;
|
||||
min-height: 600px;
|
||||
}
|
||||
|
||||
.modal.wide .dialogModal-body {
|
||||
min-width: 800px;
|
||||
}
|
||||
|
||||
.dialog-message-and-page-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dialogModal-page-container {
|
||||
flex: 1 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dialogModal-pane {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.dialogModal-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.footer-button.dialogModal-hidden {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.footer-button .validating {
|
||||
background-size: 15px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.vs .footer-button .validating {
|
||||
background-image: url("loading.svg");
|
||||
}
|
||||
|
||||
.vs-dark .footer-button .validating,
|
||||
.hc-black .footer-button .validating {
|
||||
background-image: url("loading_inverse.svg");
|
||||
}
|
||||
|
||||
.dialogModal-wizardHeader {
|
||||
padding: 10px 30px;
|
||||
}
|
||||
|
||||
.dialogModal-wizardHeader h1 {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 3px;
|
||||
font-size: 1.5em;
|
||||
font-weight: lighter;
|
||||
}
|
||||
|
||||
.dialogContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
31
src/sql/parts/query/modelViewTab/media/loading.svg
Normal file
31
src/sql/parts/query/modelViewTab/media/loading.svg
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version='1.0' standalone='no' ?>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='10px' height='10px'>
|
||||
<style>
|
||||
circle {
|
||||
animation: ball 0.6s linear infinite;
|
||||
}
|
||||
|
||||
circle:nth-child(2) { animation-delay: 0.075s; }
|
||||
circle:nth-child(3) { animation-delay: 0.15s; }
|
||||
circle:nth-child(4) { animation-delay: 0.225s; }
|
||||
circle:nth-child(5) { animation-delay: 0.3s; }
|
||||
circle:nth-child(6) { animation-delay: 0.375s; }
|
||||
circle:nth-child(7) { animation-delay: 0.45s; }
|
||||
circle:nth-child(8) { animation-delay: 0.525s; }
|
||||
|
||||
@keyframes ball {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0.3; }
|
||||
}
|
||||
</style>
|
||||
<g>
|
||||
<circle cx='5' cy='1' r='1' style='opacity:0.3;' />
|
||||
<circle cx='7.8284' cy='2.1716' r='1' style='opacity:0.3;' />
|
||||
<circle cx='9' cy='5' r='1' style='opacity:0.3;' />
|
||||
<circle cx='7.8284' cy='7.8284' r='1' style='opacity:0.3;' />
|
||||
<circle cx='5' cy='9' r='1' style='opacity:0.3;' />
|
||||
<circle cx='2.1716' cy='7.8284' r='1' style='opacity:0.3;' />
|
||||
<circle cx='1' cy='5' r='1' style='opacity:0.3;' />
|
||||
<circle cx='2.1716' cy='2.1716' r='1' style='opacity:0.3;' />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
31
src/sql/parts/query/modelViewTab/media/loading_inverse.svg
Normal file
31
src/sql/parts/query/modelViewTab/media/loading_inverse.svg
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version='1.0' standalone='no' ?>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='10px' height='10px'>
|
||||
<style>
|
||||
circle {
|
||||
animation: ball 0.6s linear infinite;
|
||||
}
|
||||
|
||||
circle:nth-child(2) { animation-delay: 0.075s; }
|
||||
circle:nth-child(3) { animation-delay: 0.15s; }
|
||||
circle:nth-child(4) { animation-delay: 0.225s; }
|
||||
circle:nth-child(5) { animation-delay: 0.3s; }
|
||||
circle:nth-child(6) { animation-delay: 0.375s; }
|
||||
circle:nth-child(7) { animation-delay: 0.45s; }
|
||||
circle:nth-child(8) { animation-delay: 0.525s; }
|
||||
|
||||
@keyframes ball {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0.3; }
|
||||
}
|
||||
</style>
|
||||
<g style="fill:white;">
|
||||
<circle cx='5' cy='1' r='1' style='opacity:0.3;' />
|
||||
<circle cx='7.8284' cy='2.1716' r='1' style='opacity:0.3;' />
|
||||
<circle cx='9' cy='5' r='1' style='opacity:0.3;' />
|
||||
<circle cx='7.8284' cy='7.8284' r='1' style='opacity:0.3;' />
|
||||
<circle cx='5' cy='9' r='1' style='opacity:0.3;' />
|
||||
<circle cx='2.1716' cy='7.8284' r='1' style='opacity:0.3;' />
|
||||
<circle cx='1' cy='5' r='1' style='opacity:0.3;' />
|
||||
<circle cx='2.1716' cy='2.1716' r='1' style='opacity:0.3;' />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
93
src/sql/parts/query/modelViewTab/media/wizardNavigation.css
Normal file
93
src/sql/parts/query/modelViewTab/media/wizardNavigation.css
Normal file
@@ -0,0 +1,93 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.wizardNavigation-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 80px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hc-black .wizardNavigation-container {
|
||||
border-right-color: #2b56f2;
|
||||
border-right-style: solid;
|
||||
border-right-width: 1px;
|
||||
background-color: unset;
|
||||
}
|
||||
|
||||
.wizardNavigation-pageNumber {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
max-height: 100px;
|
||||
}
|
||||
|
||||
.wizardNavigation-pageNumber a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.wizardNavigation-dot {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
background-color: rgb(200, 200, 200);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
border-style: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.hc-black .wizardNavigation-dot {
|
||||
flex-grow: 1;
|
||||
background-color: unset;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
.wizardNavigation-connector {
|
||||
width: 3px;
|
||||
display: inline-block;
|
||||
flex-grow: 1;
|
||||
background-color: rgb(200, 200, 200);
|
||||
}
|
||||
|
||||
.hc-black .wizardNavigation-connector {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.wizardNavigation-connector.active,
|
||||
.wizardNavigation-dot.active {
|
||||
background-color: rgb(9, 109, 201);
|
||||
}
|
||||
|
||||
.hc-black .wizardNavigation-dot.active {
|
||||
border-color: #2b56f2;
|
||||
background-color: unset;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.hc-black .wizardNavigation-dot.active:hover,
|
||||
.hc-black .wizardNavigation-dot.active.currentPage:hover {
|
||||
border-color: #F38518;
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.wizardNavigation-dot.active.currentPage {
|
||||
border-style: double;
|
||||
}
|
||||
|
||||
.hc-black .wizardNavigation-dot.active.currentPage {
|
||||
border-style: solid;
|
||||
border-color: #F38518;
|
||||
}
|
||||
|
||||
.wizardNavigation-connector.invisible {
|
||||
visibility: hidden;
|
||||
}
|
||||
79
src/sql/parts/query/modelViewTab/queryModelViewTab.module.ts
Normal file
79
src/sql/parts/query/modelViewTab/queryModelViewTab.module.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 'vs/css!./media/dialogModal';
|
||||
|
||||
import { forwardRef, NgModule, ComponentFactoryResolver, Inject, ApplicationRef } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { CommonModule, APP_BASE_HREF } from '@angular/common';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { WizardNavigation } from 'sql/platform/dialog/wizardNavigation.component';
|
||||
import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry';
|
||||
import { ModelViewContent } from 'sql/parts/modelComponents/modelViewContent.component';
|
||||
import { ModelComponentWrapper } from 'sql/parts/modelComponents/modelComponentWrapper.component';
|
||||
import { ComponentHostDirective } from 'sql/parts/dashboard/common/componentHost.directive';
|
||||
import { IBootstrapParams, ISelector, providerIterator } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox.component';
|
||||
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox.component';
|
||||
import { EditableDropDown } from 'sql/base/browser/ui/editableDropdown/editableDropdown.component';
|
||||
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox.component';
|
||||
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { QueryModelViewTabContainer } from 'sql/parts/query/modelViewTab/queryModelViewTabContainer.component';
|
||||
|
||||
export const QueryModelViewTabModule = (params, selector: string, instantiationService: IInstantiationService): any => {
|
||||
|
||||
/* Model-backed components */
|
||||
let extensionComponents = Registry.as<IComponentRegistry>(Extensions.ComponentContribution).getAllCtors();
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
Checkbox,
|
||||
SelectBox,
|
||||
EditableDropDown,
|
||||
InputBox,
|
||||
QueryModelViewTabContainer,
|
||||
WizardNavigation,
|
||||
ModelViewContent,
|
||||
ModelComponentWrapper,
|
||||
ComponentHostDirective,
|
||||
...extensionComponents
|
||||
],
|
||||
entryComponents: [QueryModelViewTabContainer, WizardNavigation, ...extensionComponents],
|
||||
imports: [
|
||||
FormsModule,
|
||||
CommonModule,
|
||||
BrowserModule
|
||||
],
|
||||
providers: [
|
||||
{ provide: APP_BASE_HREF, useValue: '/' },
|
||||
CommonServiceInterface,
|
||||
{ provide: IBootstrapParams, useValue: params },
|
||||
{ provide: ISelector, useValue: selector },
|
||||
...providerIterator(instantiationService)
|
||||
]
|
||||
})
|
||||
class ModuleClass {
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver,
|
||||
@Inject(ISelector) private selector: string
|
||||
) {
|
||||
}
|
||||
|
||||
ngDoBootstrap(appRef: ApplicationRef) {
|
||||
let componentClass = QueryModelViewTabContainer;
|
||||
const factoryWrapper: any = this._resolver.resolveComponentFactory<QueryModelViewTabContainer>(componentClass);
|
||||
factoryWrapper.factory.selector = this.selector;
|
||||
appRef.bootstrap(factoryWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
return ModuleClass;
|
||||
};
|
||||
67
src/sql/parts/query/modelViewTab/queryModelViewTab.ts
Normal file
67
src/sql/parts/query/modelViewTab/queryModelViewTab.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { Dimension } from 'vs/base/browser/dom';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IPanelView, IPanelTab } from 'sql/base/browser/ui/panel/panel';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { bootstrapAngular } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { QueryModelViewTabModule } from 'sql/parts/query/modelViewTab/queryModelViewTab.module';
|
||||
|
||||
export class QueryModelViewTab implements IPanelTab {
|
||||
public identifier = 'QueryModelViewTab_';
|
||||
public readonly view: QueryModelViewTabView;
|
||||
|
||||
constructor(public title: string, @IInstantiationService instantiationService: IInstantiationService) {
|
||||
this.identifier += title;
|
||||
this.view = instantiationService.createInstance(QueryModelViewTabView);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
dispose(this.view);
|
||||
}
|
||||
|
||||
public clear() {
|
||||
this.view.clear();
|
||||
}
|
||||
}
|
||||
|
||||
export class QueryModelViewTabView implements IPanelView {
|
||||
|
||||
public _componentId: string;
|
||||
private _isInitialized: boolean = false;
|
||||
|
||||
private _selector: string;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private _instantiationService: IInstantiationService) {
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
this.bootstrapAngular(container);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
}
|
||||
|
||||
public clear() {
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the angular components and record for this input that we have done so
|
||||
*/
|
||||
private bootstrapAngular(container: HTMLElement): string {
|
||||
let uniqueSelector = bootstrapAngular(this._instantiationService,
|
||||
QueryModelViewTabModule,
|
||||
container,
|
||||
'querytab-modelview-container',
|
||||
{ modelViewId: this._componentId });
|
||||
return uniqueSelector;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 'vs/css!./media/dialogModal';
|
||||
import { Component, ViewChild, Inject, forwardRef, ElementRef, AfterViewInit } from '@angular/core';
|
||||
import { ModelViewContent } from 'sql/parts/modelComponents/modelViewContent.component';
|
||||
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { DialogPane } from 'sql/platform/dialog/dialogPane';
|
||||
import { ComponentEventType } from 'sql/parts/modelComponents/interfaces';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
|
||||
export interface LayoutRequestParams {
|
||||
modelViewId?: string;
|
||||
alwaysRefresh?: boolean;
|
||||
}
|
||||
export interface DialogComponentParams extends IBootstrapParams {
|
||||
modelViewId: string;
|
||||
validityChangedCallback: (valid: boolean) => void;
|
||||
onLayoutRequested: Event<LayoutRequestParams>;
|
||||
dialogPane: DialogPane;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'querytab-modelview-container',
|
||||
providers: [],
|
||||
template: `
|
||||
<modelview-content [modelViewId]="modelViewId">
|
||||
</modelview-content>
|
||||
`
|
||||
})
|
||||
export class QueryModelViewTabContainer implements AfterViewInit {
|
||||
private _onResize = new Emitter<void>();
|
||||
public readonly onResize: Event<void> = this._onResize.event;
|
||||
private _dialogPane: DialogPane;
|
||||
|
||||
public modelViewId: string;
|
||||
@ViewChild(ModelViewContent) private _modelViewContent: ModelViewContent;
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
|
||||
@Inject(IBootstrapParams) private _params: DialogComponentParams) {
|
||||
this.modelViewId = this._params.modelViewId;
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this._modelViewContent.onEvent(event => {
|
||||
if (event.isRootComponent && event.eventType === ComponentEventType.validityChanged) {
|
||||
this._params.validityChangedCallback(event.args);
|
||||
}
|
||||
});
|
||||
let element = <HTMLElement>this._el.nativeElement;
|
||||
element.style.height = '100%';
|
||||
element.style.width = '100%';
|
||||
}
|
||||
|
||||
public layout(): void {
|
||||
this._modelViewContent.layout();
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,8 @@ import {
|
||||
EditSubsetResult,
|
||||
EditCreateRowResult,
|
||||
EditRevertCellResult,
|
||||
ExecutionPlanOptions
|
||||
ExecutionPlanOptions,
|
||||
queryeditor
|
||||
} from 'azdata';
|
||||
import { QueryInfo } from 'sql/platform/query/common/queryModelService';
|
||||
|
||||
@@ -24,6 +25,18 @@ export const SERVICE_ID = 'queryModelService';
|
||||
|
||||
export const IQueryModelService = createDecorator<IQueryModelService>(SERVICE_ID);
|
||||
|
||||
export interface IQueryPlanInfo {
|
||||
providerId: string;
|
||||
fileUri: string;
|
||||
planXml: string;
|
||||
}
|
||||
|
||||
export interface IQueryEvent {
|
||||
type: queryeditor.QueryEvent;
|
||||
uri: string;
|
||||
params?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for the logic of handling running queries and grid interactions for all URIs.
|
||||
*/
|
||||
@@ -56,7 +69,7 @@ export interface IQueryModelService {
|
||||
|
||||
onRunQueryStart: Event<string>;
|
||||
onRunQueryComplete: Event<string>;
|
||||
|
||||
onQueryEvent: Event<IQueryEvent>;
|
||||
|
||||
// Edit Data Functions
|
||||
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): void;
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as GridContentEvents from 'sql/parts/grid/common/gridContentEvents';
|
||||
import * as LocalizedConstants from 'sql/parts/query/common/localizedConstants';
|
||||
import QueryRunner, { EventType as QREvents } from 'sql/platform/query/common/queryRunner';
|
||||
import { DataService } from 'sql/parts/grid/services/dataService';
|
||||
import { IQueryModelService } from 'sql/platform/query/common/queryModel';
|
||||
import { IQueryModelService, IQueryPlanInfo, IQueryEvent } from 'sql/platform/query/common/queryModel';
|
||||
import { QueryInput } from 'sql/parts/query/common/queryInput';
|
||||
import { QueryStatusbarItem } from 'sql/parts/query/execution/queryStatus';
|
||||
import { SqlFlavorStatusbarItem } from 'sql/parts/query/common/flavorStatus';
|
||||
@@ -68,11 +68,13 @@ export class QueryModelService implements IQueryModelService {
|
||||
private _queryInfoMap: Map<string, QueryInfo>;
|
||||
private _onRunQueryStart: Emitter<string>;
|
||||
private _onRunQueryComplete: Emitter<string>;
|
||||
private _onQueryEvent: Emitter<IQueryEvent>;
|
||||
private _onEditSessionReady: Emitter<azdata.EditSessionReadyParams>;
|
||||
|
||||
// EVENTS /////////////////////////////////////////////////////////////
|
||||
public get onRunQueryStart(): Event<string> { return this._onRunQueryStart.event; }
|
||||
public get onRunQueryComplete(): Event<string> { return this._onRunQueryComplete.event; }
|
||||
public get onQueryEvent(): Event<IQueryEvent> { return this._onQueryEvent.event; }
|
||||
public get onEditSessionReady(): Event<azdata.EditSessionReadyParams> { return this._onEditSessionReady.event; }
|
||||
|
||||
// CONSTRUCTOR /////////////////////////////////////////////////////////
|
||||
@@ -83,6 +85,7 @@ export class QueryModelService implements IQueryModelService {
|
||||
this._queryInfoMap = new Map<string, QueryInfo>();
|
||||
this._onRunQueryStart = new Emitter<string>();
|
||||
this._onRunQueryComplete = new Emitter<string>();
|
||||
this._onQueryEvent = new Emitter<IQueryEvent>();
|
||||
this._onEditSessionReady = new Emitter<azdata.EditSessionReadyParams>();
|
||||
|
||||
// Register Statusbar items
|
||||
@@ -308,13 +311,40 @@ export class QueryModelService implements IQueryModelService {
|
||||
});
|
||||
queryRunner.addListener(QREvents.COMPLETE, totalMilliseconds => {
|
||||
this._onRunQueryComplete.fire(uri);
|
||||
|
||||
// fire extensibility API event
|
||||
let event: IQueryEvent = {
|
||||
type: 'queryStop',
|
||||
uri: uri
|
||||
};
|
||||
this._onQueryEvent.fire(event);
|
||||
|
||||
// fire UI event
|
||||
this._fireQueryEvent(uri, 'complete', totalMilliseconds);
|
||||
});
|
||||
queryRunner.addListener(QREvents.START, () => {
|
||||
this._onRunQueryStart.fire(uri);
|
||||
|
||||
// fire extensibility API event
|
||||
let event: IQueryEvent = {
|
||||
type: 'queryStart',
|
||||
uri: uri
|
||||
};
|
||||
this._onQueryEvent.fire(event);
|
||||
|
||||
this._fireQueryEvent(uri, 'start');
|
||||
});
|
||||
|
||||
queryRunner.addListener(QREvents.QUERY_PLAN_AVAILABLE, (planInfo) => {
|
||||
// fire extensibility API event
|
||||
let event: IQueryEvent = {
|
||||
type: 'executionPlan',
|
||||
uri: planInfo.fileUri,
|
||||
params: planInfo
|
||||
};
|
||||
this._onQueryEvent.fire(event);
|
||||
});
|
||||
|
||||
info.queryRunner = queryRunner;
|
||||
info.dataService = this._instantiationService.createInstance(DataService, uri);
|
||||
this._queryInfoMap.set(uri, info);
|
||||
@@ -422,10 +452,26 @@ export class QueryModelService implements IQueryModelService {
|
||||
});
|
||||
queryRunner.addListener(QREvents.COMPLETE, totalMilliseconds => {
|
||||
this._onRunQueryComplete.fire(ownerUri);
|
||||
// fire extensibility API event
|
||||
let event: IQueryEvent = {
|
||||
type: 'queryStop',
|
||||
uri: ownerUri
|
||||
};
|
||||
this._onQueryEvent.fire(event);
|
||||
|
||||
// fire UI event
|
||||
this._fireQueryEvent(ownerUri, 'complete', totalMilliseconds);
|
||||
});
|
||||
queryRunner.addListener(QREvents.START, () => {
|
||||
this._onRunQueryStart.fire(ownerUri);
|
||||
// fire extensibility API event
|
||||
let event: IQueryEvent = {
|
||||
type: 'queryStart',
|
||||
uri: ownerUri
|
||||
};
|
||||
this._onQueryEvent.fire(event);
|
||||
|
||||
// fire UI event
|
||||
this._fireQueryEvent(ownerUri, 'start');
|
||||
});
|
||||
queryRunner.addListener(QREvents.EDIT_SESSION_READY, e => {
|
||||
|
||||
@@ -25,6 +25,7 @@ import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ResultSerializer } from 'sql/platform/node/resultSerializer';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IQueryPlanInfo } from 'sql/platform/query/common/queryModel';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
@@ -41,7 +42,8 @@ export const enum EventType {
|
||||
BATCH_START = 'batchStart',
|
||||
BATCH_COMPLETE = 'batchComplete',
|
||||
RESULT_SET = 'resultSet',
|
||||
EDIT_SESSION_READY = 'editSessionReady'
|
||||
EDIT_SESSION_READY = 'editSessionReady',
|
||||
QUERY_PLAN_AVAILABLE = 'queryPlanAvailable'
|
||||
}
|
||||
|
||||
export interface IEventType {
|
||||
@@ -52,6 +54,7 @@ export interface IEventType {
|
||||
batchComplete: azdata.BatchSummary;
|
||||
resultSet: azdata.ResultSetSummary;
|
||||
editSessionReady: IEditSessionReadyEvent;
|
||||
queryPlanAvailable: IQueryPlanInfo;
|
||||
}
|
||||
|
||||
export interface IGridMessage extends azdata.IResultMessage {
|
||||
@@ -363,7 +366,11 @@ export default class QueryRunner extends Disposable {
|
||||
// check if this result has show plan, this needs work, it won't work for any other provider
|
||||
let hasShowPlan = !!result.resultSetSummary.columnInfo.find(e => e.columnName === 'Microsoft SQL Server 2005 XML Showplan');
|
||||
if (hasShowPlan) {
|
||||
this.getQueryRows(0, 1, result.resultSetSummary.batchId, result.resultSetSummary.id).then(e => this._planXml.resolve(e.resultSubset.rows[0][0].displayValue));
|
||||
this.getQueryRows(0, 1, result.resultSetSummary.batchId, result.resultSetSummary.id).then(e => {
|
||||
if (e.resultSubset.rows) {
|
||||
this._planXml.resolve(e.resultSubset.rows[0][0].displayValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// we will just ignore the set if we already have it
|
||||
@@ -387,7 +394,20 @@ export default class QueryRunner extends Disposable {
|
||||
// check if this result has show plan, this needs work, it won't work for any other provider
|
||||
let hasShowPlan = !!result.resultSetSummary.columnInfo.find(e => e.columnName === 'Microsoft SQL Server 2005 XML Showplan');
|
||||
if (hasShowPlan) {
|
||||
this.getQueryRows(0, 1, result.resultSetSummary.batchId, result.resultSetSummary.id).then(e => this._planXml.resolve(e.resultSubset.rows[0][0].displayValue));
|
||||
this.getQueryRows(0, 1, result.resultSetSummary.batchId, result.resultSetSummary.id).then(e => {
|
||||
if (e.resultSubset.rows) {
|
||||
let planXmlString = e.resultSubset.rows[0][0].displayValue;
|
||||
this._planXml.resolve(e.resultSubset.rows[0][0].displayValue);
|
||||
// fire query plan available event if execution is completed
|
||||
if (result.resultSetSummary.complete) {
|
||||
this._eventEmitter.emit(EventType.QUERY_PLAN_AVAILABLE, {
|
||||
providerId: 'MSSQL',
|
||||
fileUri: result.ownerUri,
|
||||
planXml: planXmlString
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (batchSet) {
|
||||
|
||||
@@ -7,11 +7,35 @@
|
||||
import { IMainContext } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostQueryEditorShape, SqlMainContext, MainThreadQueryEditorShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { IQueryEvent } from 'sql/platform/query/common/queryModel';
|
||||
|
||||
class ExtHostQueryDocument implements azdata.queryeditor.QueryDocument {
|
||||
constructor(
|
||||
public providerId: string,
|
||||
public uri: string,
|
||||
private _proxy: MainThreadQueryEditorShape) {
|
||||
}
|
||||
|
||||
// get the document's execution options
|
||||
getOptions(): Map<string, string> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// set the document's execution optionsß
|
||||
setOptions(options: Map<string, string>): void {
|
||||
|
||||
}
|
||||
|
||||
createQueryTab(tab: azdata.window.DialogTab): void {
|
||||
this._proxy.$createQueryTab(this.uri, tab.title, tab.content);
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostQueryEditor implements ExtHostQueryEditorShape {
|
||||
|
||||
private _proxy: MainThreadQueryEditorShape;
|
||||
private _nextListenerHandle: number = 0;
|
||||
private _queryListeners = new Map<number, azdata.queryeditor.QueryEventListener>();
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext
|
||||
@@ -26,4 +50,17 @@ export class ExtHostQueryEditor implements ExtHostQueryEditorShape {
|
||||
public $runQuery(fileUri: string): void {
|
||||
return this._proxy.$runQuery(fileUri);
|
||||
}
|
||||
|
||||
public $registerQueryInfoListener(providerId: string, listener: azdata.queryeditor.QueryEventListener): void {
|
||||
this._queryListeners[this._nextListenerHandle] = listener;
|
||||
this._proxy.$registerQueryInfoListener(this._nextListenerHandle, providerId);
|
||||
this._nextListenerHandle++;
|
||||
}
|
||||
|
||||
public $onQueryEvent(handle: number, fileUri:string, event: IQueryEvent): void {
|
||||
let listener: azdata.queryeditor.QueryEventListener = this._queryListeners[handle];
|
||||
if (listener) {
|
||||
listener.onQueryEvent(event.type, new ExtHostQueryDocument('MSSQL', fileUri, this._proxy), event.params.planXml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/q
|
||||
import { QueryEditor } from 'sql/parts/query/editor/queryEditor';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IQueryModelService, IQueryEvent } from 'sql/platform/query/common/queryModel';
|
||||
|
||||
@extHostNamedCustomer(SqlMainContext.MainThreadQueryEditor)
|
||||
export class MainThreadQueryEditor implements MainThreadQueryEditorShape {
|
||||
@@ -23,6 +24,7 @@ export class MainThreadQueryEditor implements MainThreadQueryEditorShape {
|
||||
extHostContext: IExtHostContext,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@IQueryEditorService private _queryEditorService: IQueryEditorService,
|
||||
@IQueryModelService private _queryModelService: IQueryModelService,
|
||||
@IEditorService private _editorService: IEditorService
|
||||
) {
|
||||
if (extHostContext) {
|
||||
@@ -75,4 +77,24 @@ export class MainThreadQueryEditor implements MainThreadQueryEditorShape {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public $registerQueryInfoListener(handle: number, providerId: string): void {
|
||||
this._toDispose.push(this._queryModelService.onQueryEvent(event => {
|
||||
this._proxy.$onQueryEvent(handle, event.uri, event);
|
||||
}));
|
||||
}
|
||||
|
||||
public $createQueryTab(fileUri: string, title: string, componentId: string): void {
|
||||
let editors = this._editorService.visibleControls.filter(resource => {
|
||||
return !!resource && resource.input.getResource().toString() === fileUri;
|
||||
});
|
||||
|
||||
let editor = editors && editors.length > 0 ? editors[0] : undefined;
|
||||
if (editor) {
|
||||
let queryEditor = editor as QueryEditor;
|
||||
if (queryEditor) {
|
||||
queryEditor.registerQueryModelViewTab(title, componentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,13 +438,20 @@ export function createApiFactory(
|
||||
|
||||
// namespace: queryeditor
|
||||
const queryEditor: typeof azdata.queryeditor = {
|
||||
|
||||
connect(fileUri: string, connectionId: string): Thenable<void> {
|
||||
return extHostQueryEditor.$connect(fileUri, connectionId);
|
||||
},
|
||||
|
||||
runQuery(fileUri: string): void {
|
||||
runQuery(fileUri: string, options?: Map<string, string>): void {
|
||||
extHostQueryEditor.$runQuery(fileUri);
|
||||
},
|
||||
|
||||
registerQueryEventListener(listener: azdata.queryeditor.QueryEventListener): void {
|
||||
extHostQueryEditor.$registerQueryInfoListener('MSSQL', listener);
|
||||
},
|
||||
|
||||
getQueryDocument(fileUri: string): azdata.queryeditor.QueryDocument {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor';
|
||||
import { IUndoStopOptions } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { IQueryEvent } from 'sql/platform/query/common/queryModel';
|
||||
|
||||
export abstract class ExtHostAccountManagementShape {
|
||||
$autoOAuthCancelled(handle: number): Thenable<void> { throw ni(); }
|
||||
@@ -757,11 +758,14 @@ export interface MainThreadModelViewDialogShape extends IDisposable {
|
||||
$setDirty(handle: number, isDirty: boolean): void;
|
||||
}
|
||||
export interface ExtHostQueryEditorShape {
|
||||
$onQueryEvent(handle: number, fileUri:string, event: IQueryEvent): void;
|
||||
}
|
||||
|
||||
export interface MainThreadQueryEditorShape extends IDisposable {
|
||||
$connect(fileUri: string, connectionId: string): Thenable<void>;
|
||||
$runQuery(fileUri: string): void;
|
||||
$createQueryTab(fileUri: string, title: string, content: string): void;
|
||||
$registerQueryInfoListener(handle: number, providerId: string): void;
|
||||
}
|
||||
|
||||
export interface ExtHostNotebookShape {
|
||||
@@ -870,4 +874,4 @@ export interface ExtHostExtensionManagementShape {
|
||||
|
||||
export interface MainThreadExtensionManagementShape extends IDisposable {
|
||||
$install(vsixPath: string): Thenable<string>;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user