From bfa9e8c495d22ac44a284eb4db5f63afce981a69 Mon Sep 17 00:00:00 2001 From: Anthony Dresser Date: Tue, 16 Oct 2018 16:15:47 -0700 Subject: [PATCH] Handle query plan flow problems (#2918) * modify the query plan work flow to account for some errors * formatting --- src/sql/base/browser/ui/panel/panel.ts | 108 +++++++++--------- .../parts/query/editor/queryResultsView.ts | 8 +- src/sql/parts/query/execution/queryRunner.ts | 9 +- 3 files changed, 63 insertions(+), 62 deletions(-) diff --git a/src/sql/base/browser/ui/panel/panel.ts b/src/sql/base/browser/ui/panel/panel.ts index 73d8af93fc..aa5b608994 100644 --- a/src/sql/base/browser/ui/panel/panel.ts +++ b/src/sql/base/browser/ui/panel/panel.ts @@ -5,8 +5,8 @@ import { IThemable } from 'vs/platform/theme/common/styler'; import { Event, Emitter } from 'vs/base/common/event'; -import { Dimension, EventType } from 'vs/base/browser/dom'; -import { $, Builder } from 'vs/base/browser/builder'; +import { Dimension, EventType, $, addDisposableListener } from 'vs/base/browser/dom'; +import { $ as quickBuilder } from 'vs/base/browser/builder'; import { IAction } from 'vs/base/common/actions'; import { IActionOptions, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -34,8 +34,8 @@ export interface IPanelTab { } interface IInternalPanelTab extends IPanelTab { - header: Builder; - label: Builder; + header: HTMLElement; + label: HTMLElement; dispose(): void; } @@ -49,10 +49,10 @@ export class TabbedPanel extends Disposable implements IThemable { private _tabMap = new Map(); private _shownTab: PanelTabIdentifier; public readonly headersize = 35; - private $header: Builder; - private $tabList: Builder; - private $body: Builder; - private $parent: Builder; + private header: HTMLElement; + private tabList: HTMLElement; + private body: HTMLElement; + private parent: HTMLElement; private _actionbar: ActionBar; private _currentDimensions: Dimension; private _collapsed = false; @@ -65,26 +65,26 @@ export class TabbedPanel extends Disposable implements IThemable { constructor(private container: HTMLElement, private options: IPanelOptions = defaultOptions) { super(); - this.$parent = this._register($('.tabbedPanel')); - this.$parent.appendTo(container); - this.$header = $('.composite.title'); - this.$tabList = $('.tabList'); - this.$tabList.attr('role', 'tablist'); - this.$tabList.style('height', this.headersize + 'px'); - this.$header.append(this.$tabList); + this.parent = $('.tabbedPanel'); + container.appendChild(this.parent); + this.header = $('.composite.title'); + this.tabList = $('.tabList'); + this.tabList.setAttribute('role', 'tablist'); + this.tabList.style.height = this.headersize + 'px'; + this.header.appendChild(this.tabList); let actionbarcontainer = $('.title-actions'); - this._actionbar = new ActionBar(actionbarcontainer.getHTMLElement()); - this.$header.append(actionbarcontainer); + this._actionbar = new ActionBar(actionbarcontainer); + this.header.appendChild(actionbarcontainer); if (options.showHeaderWhenSingleView) { this._headerVisible = true; - this.$parent.append(this.$header); + this.parent.appendChild(this.header); } else { this._headerVisible = false; } - this.$body = $('.tabBody'); - this.$body.attr('role', 'tabpanel'); - this.$body.attr('tabindex', '0'); - this.$parent.append(this.$body); + this.body = $('.tabBody'); + this.body.setAttribute('role', 'tabpanel'); + this.body.setAttribute('tabindex', '0'); + this.parent.appendChild(this.body); } public contains(tab: IPanelTab): boolean { @@ -99,7 +99,7 @@ export class TabbedPanel extends Disposable implements IThemable { this.showTab(tab.identifier); } if (this._tabMap.size > 1 && !this._headerVisible) { - this.$parent.append(this.$header, 0); + this.parent.insertBefore(this.header, this.parent.firstChild); this._headerVisible = true; this.layout(this._currentDimensions); } @@ -116,30 +116,27 @@ export class TabbedPanel extends Disposable implements IThemable { private _createTab(tab: IInternalPanelTab): void { let tabHeaderElement = $('.tab-header'); - tabHeaderElement.attr('tabindex', '0'); - tabHeaderElement.attr('role', 'tab'); - tabHeaderElement.attr('aria-selected', 'false'); - tabHeaderElement.attr('aria-controls', tab.identifier); + tabHeaderElement.setAttribute('tabindex', '0'); + tabHeaderElement.setAttribute('role', 'tab'); + tabHeaderElement.setAttribute('aria-selected', 'false'); + tabHeaderElement.setAttribute('aria-controls', tab.identifier); let tabElement = $('.tab'); - tabHeaderElement.append(tabElement); + tabHeaderElement.appendChild(tabElement); let tabLabel = $('a.tabLabel'); - tabLabel.safeInnerHtml(tab.title); - tabElement.append(tabLabel); - tabHeaderElement.on(EventType.CLICK, e => this.showTab(tab.identifier)); - tabHeaderElement.on(EventType.KEY_DOWN, (e: KeyboardEvent) => { + tabLabel.innerText = tab.title; + tabElement.appendChild(tabLabel); + addDisposableListener(tabHeaderElement, EventType.CLICK, e => this.showTab(tab.identifier)); + addDisposableListener(tabHeaderElement, EventType.KEY_DOWN, (e: KeyboardEvent) => { let event = new StandardKeyboardEvent(e); if (event.equals(KeyCode.Enter)) { this.showTab(tab.identifier); e.stopImmediatePropagation(); } }); - this.$tabList.append(tabHeaderElement); + this.tabList.appendChild(tabHeaderElement); tab.header = tabHeaderElement; tab.label = tabLabel; - tab.dispose = () => { - tab.header.dispose(); - tab.label.dispose(); - }; + tab.dispose = () => { }; this._register(tab); } @@ -149,19 +146,20 @@ export class TabbedPanel extends Disposable implements IThemable { } if (this._shownTab) { - this._tabMap.get(this._shownTab).label.removeClass('active'); - this._tabMap.get(this._shownTab).header.removeClass('active').attr('aria-selected', 'false'); + this._tabMap.get(this._shownTab).label.classList.remove('active'); + this._tabMap.get(this._shownTab).header.classList.remove('active'); + this._tabMap.get(this._shownTab).header.setAttribute('aria-selected', 'false'); } this._shownTab = id; this.tabHistory.push(id); - this.$body.clearChildren(); + quickBuilder(this.body).empty(); let tab = this._tabMap.get(this._shownTab); - this.$body.attr('aria-labelledby', tab.identifier); - tab.label.addClass('active'); - tab.header.addClass('active'); - tab.header.attr('aria-selected', 'true'); - tab.view.render(this.$body.getHTMLElement()); + this.body.setAttribute('aria-labelledby', tab.identifier); + tab.label.classList.add('active'); + tab.header.classList.add('active'); + tab.header.setAttribute('aria-selected', 'true'); + tab.view.render(this.body); this._onTabChange.fire(id); if (this._currentDimensions) { this._layoutCurrentTab(new Dimension(this._currentDimensions.width, this._currentDimensions.height - this.headersize)); @@ -170,11 +168,11 @@ export class TabbedPanel extends Disposable implements IThemable { public removeTab(tab: PanelTabIdentifier) { let actualTab = this._tabMap.get(tab); - actualTab.header.destroy(); + quickBuilder(actualTab.header).destroy(); if (actualTab.view.remove) { actualTab.view.remove(); } - this._tabMap.get(tab).header.destroy(); + quickBuilder(this._tabMap.get(tab).header).destroy(); this._tabMap.delete(tab); if (this._shownTab === tab) { this._shownTab = undefined; @@ -192,7 +190,7 @@ export class TabbedPanel extends Disposable implements IThemable { } if (!this.options.showHeaderWhenSingleView && this._tabMap.size === 1 && this._headerVisible) { - this.$header.offDOM(); + this.header.remove(); this._headerVisible = false; this.layout(this._currentDimensions); } @@ -205,12 +203,12 @@ export class TabbedPanel extends Disposable implements IThemable { public layout(dimension: Dimension): void { if (dimension) { this._currentDimensions = dimension; - this.$parent.style('height', dimension.height + 'px'); - this.$parent.style('width', dimension.width + 'px'); - this.$header.style('width', dimension.width + 'px'); - this.$body.style('width', dimension.width + 'px'); + this.parent.style.height = dimension.height + 'px'; + this.parent.style.height = dimension.width + 'px'; + this.header.style.width = dimension.width + 'px'; + this.body.style.width = dimension.width + 'px'; const bodyHeight = dimension.height - (this._headerVisible ? this.headersize : 0); - this.$body.style('height', bodyHeight + 'px'); + this.body.style.height = bodyHeight + 'px'; this._layoutCurrentTab(new Dimension(dimension.width, bodyHeight)); } } @@ -232,9 +230,9 @@ export class TabbedPanel extends Disposable implements IThemable { this._collapsed = val === false ? false : true; if (this.collapsed) { - this.$body.offDOM(); + this.body.remove(); } else { - this.$parent.append(this.$body); + this.parent.appendChild(this.body); } } diff --git a/src/sql/parts/query/editor/queryResultsView.ts b/src/sql/parts/query/editor/queryResultsView.ts index ed973a7363..0bcbec8fee 100644 --- a/src/sql/parts/query/editor/queryResultsView.ts +++ b/src/sql/parts/query/editor/queryResultsView.ts @@ -76,7 +76,7 @@ class ResultsView implements IPanelView { this.panelViewlet.resizePanel(this.gridPanel, this.state.messagePanelSize); } this.panelViewlet.resizePanel(this.gridPanel, panelSize); - }) + }); // once the user changes the sash we should stop trying to resize the grid once(this.panelViewlet.onDidSashChange)(e => { this.needsGridResize = false; @@ -204,9 +204,11 @@ export class QueryResultsView { this._panelView.pushTab(this.qpTab); } } - this.runnerDisposables.push(queryRunner.onResultSet(() => { + this.runnerDisposables.push(queryRunner.onQueryEnd(() => { if (queryRunner.isQueryPlan) { - this.showPlan(queryRunner.planXml); + queryRunner.planXml.then(e => { + this.showPlan(e); + }); } })); if (this.input.state.activeTab) { diff --git a/src/sql/parts/query/execution/queryRunner.ts b/src/sql/parts/query/execution/queryRunner.ts index ba1530b5a2..b0450ce27b 100644 --- a/src/sql/parts/query/execution/queryRunner.ts +++ b/src/sql/parts/query/execution/queryRunner.ts @@ -26,6 +26,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ResultSerializer } from 'sql/parts/query/common/resultSerializer'; import { TPromise } from 'vs/base/common/winjs.base'; +import { Deferred } from 'sql/base/common/promise'; export interface IEditSessionReadyEvent { ownerUri: string; @@ -69,11 +70,11 @@ export default class QueryRunner { private _hasCompleted: boolean = false; private _batchSets: sqlops.BatchSummary[] = []; private _eventEmitter = new EventEmitter(); - private _isQueryPlan: boolean; + private _isQueryPlan: boolean; public get isQueryPlan(): boolean { return this._isQueryPlan; } - private _planXml: string; - public get planXml(): string { return this._planXml; } + private _planXml = new Deferred(); + public get planXml(): Thenable { return this._planXml.promise; } private _onMessage = new Emitter(); private _debouncedMessage = debounceEvent(this._onMessage.event, (l, e) => { @@ -342,7 +343,7 @@ export default class QueryRunner { } // handle getting queryPlanxml if we need too if (this.isQueryPlan) { - this.getQueryRows(0, 1, 0, 0).then(e => this._planXml = e.resultSubset.rows[0][0].displayValue); + this.getQueryRows(0, 1, 0, 0).then(e => this._planXml.resolve(e.resultSubset.rows[0][0].displayValue)); } if (batchSet) { // Store the result set in the batch and emit that a result set has completed