From 9fe423703332b475edc16597fefe7eee82f548bf Mon Sep 17 00:00:00 2001 From: Anthony Dresser Date: Thu, 13 Sep 2018 18:42:29 -0700 Subject: [PATCH] Maintain Query State (#2571) * add results view stating * working through the bugs * handle various resizing bugs * gnale resizing better * fix tests by adding missing node module * formatting * refactor interfaces out to get around testing restrictions * more refactoring of importants to avoid loading errors --- src/sql/base/browser/ui/panel/panel.ts | 18 +-- .../scrollableSplitview.ts | 8 ++ .../ui/table/plugins/cellRangeSelector.ts | 4 +- .../plugins/cellSelectionModel.plugin.ts | 2 - .../views/charts/chartInsight.component.ts | 24 +--- .../insights/views/charts/interfaces.ts | 32 +++++ .../views/charts/types/barChart.component.ts | 3 +- .../charts/types/doughnutChart.component.ts | 2 +- .../types/horizontalBarChart.component.ts | 2 +- .../views/charts/types/lineChart.component.ts | 11 +- .../views/charts/types/pieChart.component.ts | 3 +- .../charts/types/scatterChart.component.ts | 5 +- .../charts/types/timeSeriesChart.component.ts | 3 +- .../grid/views/query/chartViewer.component.ts | 5 +- .../parts/query/common/queryResultsInput.ts | 18 +++ .../query/editor/charting/chartOptions.ts | 3 +- .../parts/query/editor/charting/chartTab.ts | 2 +- .../parts/query/editor/charting/chartView.ts | 96 +++++++++++--- .../editor/charting/insights/graphInsight.ts | 2 +- .../query/editor/charting/insights/insight.ts | 8 +- .../editor/charting/insights/interfaces.ts | 5 +- src/sql/parts/query/editor/gridPanel.ts | 114 ++++++++++++++-- src/sql/parts/query/editor/messagePanel.ts | 52 +++++++- .../parts/query/editor/queryResultsView.ts | 123 ++++++++++++------ src/sql/parts/queryPlan/queryPlan.ts | 21 ++- test/all.js | 3 +- 26 files changed, 428 insertions(+), 141 deletions(-) create mode 100644 src/sql/parts/dashboard/widgets/insights/views/charts/interfaces.ts diff --git a/src/sql/base/browser/ui/panel/panel.ts b/src/sql/base/browser/ui/panel/panel.ts index 172ede6196..d997547390 100644 --- a/src/sql/base/browser/ui/panel/panel.ts +++ b/src/sql/base/browser/ui/panel/panel.ts @@ -180,14 +180,16 @@ export class TabbedPanel extends Disposable implements IThemable { } public layout(dimension: Dimension): void { - 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'); - const bodyHeight = dimension.height - (this._headerVisible ? this.headersize : 0); - this.$body.style('height', bodyHeight + 'px'); - this._layoutCurrentTab(new Dimension(dimension.width, bodyHeight)); + 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'); + const bodyHeight = dimension.height - (this._headerVisible ? this.headersize : 0); + this.$body.style('height', bodyHeight + 'px'); + this._layoutCurrentTab(new Dimension(dimension.width, bodyHeight)); + } } private _layoutCurrentTab(dimension: Dimension): void { diff --git a/src/sql/base/browser/ui/scrollableSplitview/scrollableSplitview.ts b/src/sql/base/browser/ui/scrollableSplitview/scrollableSplitview.ts index 10c735bb76..8c31e385de 100644 --- a/src/sql/base/browser/ui/scrollableSplitview/scrollableSplitview.ts +++ b/src/sql/base/browser/ui/scrollableSplitview/scrollableSplitview.ts @@ -109,6 +109,9 @@ export class ScrollableSplitView extends HeightMap implements IDisposable { private _onDidSashReset = new Emitter(); readonly onDidSashReset = this._onDidSashReset.event; + private _onScroll = new Emitter(); + readonly onScroll = this._onScroll.event; + get length(): number { return this.viewItems.length; } @@ -124,6 +127,7 @@ export class ScrollableSplitView extends HeightMap implements IDisposable { debounceEvent(this.scrollable.onScroll, (l, e) => e, 25)(e => { this.render(e.scrollTop, e.height); this.relayout(); + this._onScroll.fire(e.scrollTop); }); let domNode = this.scrollable.getDomNode(); dom.addClass(this.el, 'monaco-scroll-split-view'); @@ -330,6 +334,10 @@ export class ScrollableSplitView extends HeightMap implements IDisposable { this.resize(this.viewItems.length - 1, this.size - contentSize, undefined, lowPriorityIndex); } + public setScrollPosition(position: number) { + this.scrollable.setScrollPosition({ scrollTop: position }); + } + layout(size: number): void { const previousSize = this.size; this.size = size; diff --git a/src/sql/base/browser/ui/table/plugins/cellRangeSelector.ts b/src/sql/base/browser/ui/table/plugins/cellRangeSelector.ts index 9340b055d6..cac2e1ad46 100644 --- a/src/sql/base/browser/ui/table/plugins/cellRangeSelector.ts +++ b/src/sql/base/browser/ui/table/plugins/cellRangeSelector.ts @@ -1,7 +1,5 @@ import { mixin } from 'vs/base/common/objects'; -require.__$__nodeRequire('slickgrid/plugins/slick.cellrangedecorator'); - const defaultOptions: ICellRangeSelectorOptions = { selectionCss: { 'border': '2px dashed blue' @@ -44,6 +42,8 @@ export class CellRangeSelector implements ICellRangeSelector { public onCellRangeSelected = new Slick.Event<{ range: Slick.Range }>(); constructor(private options: ICellRangeSelectorOptions) { + require.__$__nodeRequire('slickgrid/plugins/slick.cellrangedecorator'); + this.options = mixin(this.options, defaultOptions, false); } diff --git a/src/sql/base/browser/ui/table/plugins/cellSelectionModel.plugin.ts b/src/sql/base/browser/ui/table/plugins/cellSelectionModel.plugin.ts index fcc914fd21..bf7112ef53 100644 --- a/src/sql/base/browser/ui/table/plugins/cellSelectionModel.plugin.ts +++ b/src/sql/base/browser/ui/table/plugins/cellSelectionModel.plugin.ts @@ -6,8 +6,6 @@ import { isUndefinedOrNull } from 'vs/base/common/types'; import { CellRangeSelector, ICellRangeSelector } from 'sql/base/browser/ui/table/plugins/cellRangeSelector'; -require.__$__nodeRequire('slickgrid/plugins/slick.cellrangedecorator'); - export interface ICellSelectionModelOptions { cellRangeSelector?: any; selectActiveCell?: boolean; diff --git a/src/sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component.ts b/src/sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component.ts index aeeca93863..d715355353 100644 --- a/src/sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component.ts +++ b/src/sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component.ts @@ -10,6 +10,7 @@ import * as TelemetryUtils from 'sql/common/telemetryUtilities'; import { IInsightsView, IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces'; import { memoize, unmemoize } from 'sql/base/common/decorators'; import { mixin } from 'sql/base/common/objects'; +import { LegendPosition, DataDirection, ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; import * as colors from 'vs/platform/theme/common/colorRegistry'; import { Color } from 'vs/base/common/color'; @@ -21,29 +22,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; declare var Chart: any; -export enum ChartType { - Bar = 'bar', - Doughnut = 'doughnut', - HorizontalBar = 'horizontalBar', - Line = 'line', - Pie = 'pie', - TimeSeries = 'timeSeries', - Scatter = 'scatter' -} - -export enum DataDirection { - Vertical = 'vertical', - Horizontal = 'horizontal' -} - -export enum LegendPosition { - Top = 'top', - Bottom = 'bottom', - Left = 'left', - Right = 'right', - None = 'none' -} - export function customMixin(destination: any, source: any, overwrite?: boolean): any { if (types.isObject(source)) { mixin(destination, source, overwrite, customMixin); diff --git a/src/sql/parts/dashboard/widgets/insights/views/charts/interfaces.ts b/src/sql/parts/dashboard/widgets/insights/views/charts/interfaces.ts new file mode 100644 index 0000000000..1574ccb98f --- /dev/null +++ b/src/sql/parts/dashboard/widgets/insights/views/charts/interfaces.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export enum ChartType { + Bar = 'bar', + Doughnut = 'doughnut', + HorizontalBar = 'horizontalBar', + Line = 'line', + Pie = 'pie', + TimeSeries = 'timeSeries', + Scatter = 'scatter' +} + +export enum DataDirection { + Vertical = 'vertical', + Horizontal = 'horizontal' +} + +export enum LegendPosition { + Top = 'top', + Bottom = 'bottom', + Left = 'left', + Right = 'right', + None = 'none' +} + +export enum DataType { + Number = 'number', + Point = 'point' +} \ No newline at end of file diff --git a/src/sql/parts/dashboard/widgets/insights/views/charts/types/barChart.component.ts b/src/sql/parts/dashboard/widgets/insights/views/charts/types/barChart.component.ts index 27b69e3c8a..979cca1c7f 100644 --- a/src/sql/parts/dashboard/widgets/insights/views/charts/types/barChart.component.ts +++ b/src/sql/parts/dashboard/widgets/insights/views/charts/types/barChart.component.ts @@ -3,8 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ChartInsight, ChartType, customMixin, IChartConfig } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component'; +import { ChartInsight, customMixin, IChartConfig } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component'; import { mixin } from 'sql/base/common/objects'; +import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; import { IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import * as colors from 'vs/platform/theme/common/colorRegistry'; diff --git a/src/sql/parts/dashboard/widgets/insights/views/charts/types/doughnutChart.component.ts b/src/sql/parts/dashboard/widgets/insights/views/charts/types/doughnutChart.component.ts index 37faf4eba6..91d85fd7b4 100644 --- a/src/sql/parts/dashboard/widgets/insights/views/charts/types/doughnutChart.component.ts +++ b/src/sql/parts/dashboard/widgets/insights/views/charts/types/doughnutChart.component.ts @@ -3,8 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component'; import PieChart from './pieChart.component'; +import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; export default class DoughnutChart extends PieChart { protected readonly chartType: ChartType = ChartType.Doughnut; diff --git a/src/sql/parts/dashboard/widgets/insights/views/charts/types/horizontalBarChart.component.ts b/src/sql/parts/dashboard/widgets/insights/views/charts/types/horizontalBarChart.component.ts index 2937513ac0..381b78d5a2 100644 --- a/src/sql/parts/dashboard/widgets/insights/views/charts/types/horizontalBarChart.component.ts +++ b/src/sql/parts/dashboard/widgets/insights/views/charts/types/horizontalBarChart.component.ts @@ -3,8 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component'; import BarChart from './barChart.component'; +import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; export default class HorizontalBarChart extends BarChart { protected readonly chartType: ChartType = ChartType.HorizontalBar; diff --git a/src/sql/parts/dashboard/widgets/insights/views/charts/types/lineChart.component.ts b/src/sql/parts/dashboard/widgets/insights/views/charts/types/lineChart.component.ts index 748c370990..ed0a7d8d81 100644 --- a/src/sql/parts/dashboard/widgets/insights/views/charts/types/lineChart.component.ts +++ b/src/sql/parts/dashboard/widgets/insights/views/charts/types/lineChart.component.ts @@ -3,16 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ChartType, customMixin, defaultChartConfig, IDataSet, IPointDataSet } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component'; +import { mixin } from 'vs/base/common/objects'; + +import { defaultChartConfig, IDataSet, IPointDataSet } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component'; import BarChart, { IBarChartConfig } from './barChart.component'; import { memoize, unmemoize } from 'sql/base/common/decorators'; -import { mixin } from 'vs/base/common/objects'; import { clone } from 'sql/base/common/objects'; - -export enum DataType { - Number = 'number', - Point = 'point' -} +import { ChartType, DataType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; export interface ILineConfig extends IBarChartConfig { dataType?: DataType; diff --git a/src/sql/parts/dashboard/widgets/insights/views/charts/types/pieChart.component.ts b/src/sql/parts/dashboard/widgets/insights/views/charts/types/pieChart.component.ts index 6186d10130..0e7b63e71f 100644 --- a/src/sql/parts/dashboard/widgets/insights/views/charts/types/pieChart.component.ts +++ b/src/sql/parts/dashboard/widgets/insights/views/charts/types/pieChart.component.ts @@ -3,7 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ChartInsight, ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component'; +import { ChartInsight } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component'; +import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; export default class PieChart extends ChartInsight { protected readonly chartType: ChartType = ChartType.Pie; diff --git a/src/sql/parts/dashboard/widgets/insights/views/charts/types/scatterChart.component.ts b/src/sql/parts/dashboard/widgets/insights/views/charts/types/scatterChart.component.ts index e2066a10b5..9fbbb64ba3 100644 --- a/src/sql/parts/dashboard/widgets/insights/views/charts/types/scatterChart.component.ts +++ b/src/sql/parts/dashboard/widgets/insights/views/charts/types/scatterChart.component.ts @@ -3,11 +3,12 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ChartType, defaultChartConfig } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component'; +import { defaultChartConfig } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component'; import LineChart, { ILineConfig } from './lineChart.component'; +import { clone } from 'sql/base/common/objects'; +import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; import { mixin } from 'vs/base/common/objects'; -import { clone } from 'sql/base/common/objects'; const defaultScatterConfig = mixin(clone(defaultChartConfig), { dataType: 'point', dataDirection: 'horizontal' }) as ILineConfig; diff --git a/src/sql/parts/dashboard/widgets/insights/views/charts/types/timeSeriesChart.component.ts b/src/sql/parts/dashboard/widgets/insights/views/charts/types/timeSeriesChart.component.ts index a2f40f372d..646cf6b11f 100644 --- a/src/sql/parts/dashboard/widgets/insights/views/charts/types/timeSeriesChart.component.ts +++ b/src/sql/parts/dashboard/widgets/insights/views/charts/types/timeSeriesChart.component.ts @@ -3,9 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { defaultChartConfig, IPointDataSet, ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component'; +import { defaultChartConfig, IPointDataSet } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component'; import LineChart, { ILineConfig } from './lineChart.component'; import { clone } from 'sql/base/common/objects'; +import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; import { mixin } from 'vs/base/common/objects'; import { Color } from 'vs/base/common/color'; diff --git a/src/sql/parts/grid/views/query/chartViewer.component.ts b/src/sql/parts/grid/views/query/chartViewer.component.ts index 8b79d772a5..ffc84af279 100644 --- a/src/sql/parts/grid/views/query/chartViewer.component.ts +++ b/src/sql/parts/grid/views/query/chartViewer.component.ts @@ -16,7 +16,7 @@ import { IGridDataSet } from 'sql/parts/grid/common/interfaces'; import { IInsightData, IInsightsView, IInsightsConfig } from 'sql/parts/dashboard/widgets/insights/interfaces'; import { Extensions, IInsightRegistry } from 'sql/platform/dashboard/common/insightRegistry'; import { QueryEditor } from 'sql/parts/query/editor/queryEditor'; -import { DataType, ILineConfig } from 'sql/parts/dashboard/widgets/insights/views/charts/types/lineChart.component'; +import { ILineConfig } from 'sql/parts/dashboard/widgets/insights/views/charts/types/lineChart.component'; import * as PathUtilities from 'sql/common/pathUtilities'; import { IChartViewActionContext, CopyAction, CreateInsightAction, SaveImageAction } from 'sql/parts/grid/views/query/chartViewerActions'; import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils'; @@ -24,10 +24,11 @@ import * as Constants from 'sql/parts/query/common/constants'; import { SelectBox as AngularSelectBox } from 'sql/base/browser/ui/selectBox/selectBox.component'; import { IQueryModelService } from 'sql/parts/query/execution/queryModel'; import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService'; +import { LegendPosition, DataDirection, DataType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; /* Insights */ import { - ChartInsight, DataDirection, LegendPosition + ChartInsight } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component'; import { IDisposable } from 'vs/base/common/lifecycle'; diff --git a/src/sql/parts/query/common/queryResultsInput.ts b/src/sql/parts/query/common/queryResultsInput.ts index 4873c63e83..f51f6a6caf 100644 --- a/src/sql/parts/query/common/queryResultsInput.ts +++ b/src/sql/parts/query/common/queryResultsInput.ts @@ -10,6 +10,22 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { EditorInput } from 'vs/workbench/common/editor'; import { Emitter } from 'vs/base/common/event'; +import { GridPanelState } from 'sql/parts/query/editor/gridPanel'; +import { MessagePanelState } from 'sql/parts/query/editor/messagePanel'; +import { QueryPlanState } from 'sql/parts/queryPlan/queryPlan'; +import { ChartState } from 'sql/parts/query/editor/charting/chartView'; + +export class ResultsViewState { + public gridPanelState: GridPanelState = new GridPanelState(); + public messagePanelState: MessagePanelState = new MessagePanelState(); + public chartState: ChartState = new ChartState(); + public queryPlanState: QueryPlanState = new QueryPlanState(); + public gridPanelSize: number; + public messagePanelSize: number; + public activeTab: string; + public visibleTabs: Set = new Set(); +} + /** * Input for the QueryResultsEditor. This input helps with logic for the viewing and editing of * data in the results grid. @@ -29,6 +45,8 @@ export class QueryResultsInput extends EditorInput { public readonly onRestoreViewStateEmitter = new Emitter(); public readonly onSaveViewStateEmitter = new Emitter(); + public readonly state = new ResultsViewState(); + constructor(private _uri: string) { super(); this._visible = false; diff --git a/src/sql/parts/query/editor/charting/chartOptions.ts b/src/sql/parts/query/editor/charting/chartOptions.ts index 5c8a2f2589..fed3c591f6 100644 --- a/src/sql/parts/query/editor/charting/chartOptions.ts +++ b/src/sql/parts/query/editor/charting/chartOptions.ts @@ -8,10 +8,9 @@ import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ChartType, DataDirection, LegendPosition } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component'; import { Extensions, IInsightRegistry } from 'sql/platform/dashboard/common/insightRegistry'; -import { DataType } from 'sql/parts/dashboard/widgets/insights/views/charts/types/lineChart.component'; import { InsightType, IInsightOptions } from './insights/interfaces'; +import { DataDirection, ChartType, LegendPosition, DataType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; const insightRegistry = Registry.as(Extensions.InsightContribution); diff --git a/src/sql/parts/query/editor/charting/chartTab.ts b/src/sql/parts/query/editor/charting/chartTab.ts index ef4f63faf9..11bfc8d433 100644 --- a/src/sql/parts/query/editor/charting/chartTab.ts +++ b/src/sql/parts/query/editor/charting/chartTab.ts @@ -15,7 +15,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti export class ChartTab implements IPanelTab { public readonly title = localize('chartTabTitle', 'Chart'); - public readonly identifier = generateUuid(); + public readonly identifier = 'ChartTab'; public readonly view: ChartView; constructor(@IInstantiationService instantiationService: IInstantiationService) { diff --git a/src/sql/parts/query/editor/charting/chartView.ts b/src/sql/parts/query/editor/charting/chartView.ts index 7b6a3827ca..e7749d6dea 100644 --- a/src/sql/parts/query/editor/charting/chartView.ts +++ b/src/sql/parts/query/editor/charting/chartView.ts @@ -12,11 +12,11 @@ import { Insight } from './insights/insight'; import QueryRunner from 'sql/parts/query/execution/queryRunner'; import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces'; import { ChartOptions, IChartOption, ControlType } from './chartOptions'; -import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component'; import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox'; import { IInsightOptions } from './insights/interfaces'; import { CopyAction, SaveImageAction, CreateInsightAction, IChartActionContext } from './actions'; import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar'; +import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; import { Dimension, $, getContentHeight, getContentWidth } from 'vs/base/browser/dom'; import { SelectBox } from 'vs/base/browser/ui/selectBox/selectBox'; @@ -27,6 +27,14 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { attachSelectBoxStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { isUndefinedOrNull } from 'vs/base/common/types'; + +export class ChartState { + dataId: { batchId: number, resultId: number }; + options: IInsightOptions = { + type: ChartType.Bar + }; +} declare class Proxy { constructor(object, handler); @@ -43,6 +51,8 @@ export class ChartView implements IPanelView { private _copyAction: CopyAction; private _saveAction: SaveImageAction; + private _state: ChartState; + private options: IInsightOptions = { type: ChartType.Bar }; @@ -61,7 +71,7 @@ export class ChartView implements IPanelView { private chartingContainer: HTMLElement; private optionDisposables: IDisposable[] = []; - private optionMap: { [x: string]: HTMLElement } = {}; + private optionMap: { [x: string]: { element: HTMLElement; set: (val) => void } } = {}; constructor( @IContextViewService private _contextViewService: IContextViewService, @@ -95,6 +105,10 @@ export class ChartView implements IPanelView { } let result = Reflect.set(target, key, value, receiver); + // mirror the change in our state + if (self.state) { + Reflect.set(self.state.options, key, value); + } if (change) { self.taskbar.context = { options: self.options, insight: self.insight ? self.insight.insight : undefined }; @@ -138,6 +152,7 @@ export class ChartView implements IPanelView { } public chart(dataId: { batchId: number, resultId: number }) { + this.state.dataId = dataId; this._currentData = dataId; this.shouldGraph(); } @@ -180,7 +195,9 @@ export class ChartView implements IPanelView { private buildOptions() { dispose(this.optionDisposables); this.optionDisposables = []; - this.optionMap = {}; + this.optionMap = { + 'type': this.optionMap['type'] + }; new Builder(this.typeControls).clearChildren(); this.updateActionbar(); @@ -200,9 +217,9 @@ export class ChartView implements IPanelView { let option = ChartOptions[this.options.type].find(e => e.configEntry === key); if (option && option.if) { if (option.if(this.options)) { - new Builder(this.optionMap[key]).show(); + new Builder(this.optionMap[key].element).show(); } else { - new Builder(this.optionMap[key]).hide(); + new Builder(this.optionMap[key].element).hide(); } } } @@ -227,57 +244,104 @@ export class ChartView implements IPanelView { label.innerText = option.label; let optionContainer = $('div.option-container'); optionContainer.appendChild(label); + let setFunc: (val) => void; + let value = this.state ? this.state.options[option.configEntry] || option.default : option.default; switch (option.type) { case ControlType.checkbox: let checkbox = new Checkbox(optionContainer, { label: '', ariaLabel: option.label, - checked: option.default, + checked: value, onChange: () => { if (this.options[option.configEntry] !== checkbox.checked) { this.options[option.configEntry] = checkbox.checked; - this.insight.options = this.options; + if (this.insight) { + this.insight.options = this.options; + } } } }); + setFunc = (val: boolean) => { + checkbox.checked = val; + }; break; case ControlType.combo: let dropdown = new SelectBox(option.displayableOptions || option.options, 0, this._contextViewService); - dropdown.select(option.options.indexOf(option.default)); + dropdown.select(option.options.indexOf(value)); dropdown.render(optionContainer); dropdown.onDidSelect(e => { if (this.options[option.configEntry] !== option.options[e.index]) { this.options[option.configEntry] = option.options[e.index]; - this.insight.options = this.options; + if (this.insight) { + this.insight.options = this.options; + } } }); + setFunc = (val: string) => { + if (!isUndefinedOrNull(val)) { + dropdown.select(option.options.indexOf(val)); + } + }; this.optionDisposables.push(attachSelectBoxStyler(dropdown, this._themeService)); break; case ControlType.input: let input = new InputBox(optionContainer, this._contextViewService); - input.value = option.default || ''; + input.value = value || ''; input.onDidChange(e => { if (this.options[option.configEntry] !== e) { this.options[option.configEntry] = e; - this.insight.options = this.options; + if (this.insight) { + this.insight.options = this.options; + } } }); + setFunc = (val: string) => { + if (!isUndefinedOrNull(val)) { + input.value = val; + } + }; this.optionDisposables.push(attachInputBoxStyler(input, this._themeService)); break; case ControlType.numberInput: let numberInput = new InputBox(optionContainer, this._contextViewService, { type: 'number' }); - numberInput.value = option.default || ''; + numberInput.value = value || ''; numberInput.onDidChange(e => { if (this.options[option.configEntry] !== Number(e)) { this.options[option.configEntry] = Number(e); - this.insight.options = this.options; + if (this.insight) { + this.insight.options = this.options; + } } }); + setFunc = (val: string) => { + if (!isUndefinedOrNull(val)) { + input.value = val; + } + }; this.optionDisposables.push(attachInputBoxStyler(numberInput, this._themeService)); break; } - this.optionMap[option.configEntry] = optionContainer; + this.optionMap[option.configEntry] = { element: optionContainer, set: setFunc }; container.appendChild(optionContainer); - this.options[option.configEntry] = option.default; + this.options[option.configEntry] = value; } -} \ No newline at end of file + + public set state(val: ChartState) { + this._state = val; + if (this.state.options) { + for (let key in this.state.options) { + if (this.state.options.hasOwnProperty(key)) { + this.options[key] = this.state.options[key]; + this.optionMap[key].set(this.state.options[key]); + } + } + } + if (this.state.dataId) { + this.chart(this.state.dataId); + } + } + + public get state(): ChartState { + return this._state; + } +} diff --git a/src/sql/parts/query/editor/charting/insights/graphInsight.ts b/src/sql/parts/query/editor/charting/insights/graphInsight.ts index 08b7e9b75a..b20a1d5e21 100644 --- a/src/sql/parts/query/editor/charting/insights/graphInsight.ts +++ b/src/sql/parts/query/editor/charting/insights/graphInsight.ts @@ -13,9 +13,9 @@ import * as colors from 'vs/platform/theme/common/colorRegistry'; import { editorLineNumbers } from 'vs/editor/common/view/editorColorRegistry'; import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; -import { ChartType, DataDirection, LegendPosition } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component'; import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces'; import { IInsightOptions, IInsight } from './interfaces'; +import { ChartType, DataDirection, LegendPosition } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; const noneLineGraphs = [ChartType.Doughnut, ChartType.Pie]; diff --git a/src/sql/parts/query/editor/charting/insights/insight.ts b/src/sql/parts/query/editor/charting/insights/insight.ts index 25257f4a4f..926f7a87d7 100644 --- a/src/sql/parts/query/editor/charting/insights/insight.ts +++ b/src/sql/parts/query/editor/charting/insights/insight.ts @@ -6,15 +6,15 @@ 'use strict'; import { Graph } from './graphInsight'; -import { ChartType, DataDirection } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component'; import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces'; - -import { Builder } from 'vs/base/browser/builder'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { DataDirection, ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; import { ImageInsight } from './imageInsight'; import { TableInsight } from './tableInsight'; import { IInsightOptions, IInsight, InsightType, IInsightCtor } from './interfaces'; import { CountInsight } from './countInsight'; + +import { Builder } from 'vs/base/browser/builder'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Dimension } from 'vs/base/browser/dom'; const defaultOptions: IInsightOptions = { diff --git a/src/sql/parts/query/editor/charting/insights/interfaces.ts b/src/sql/parts/query/editor/charting/insights/interfaces.ts index f01e78dc36..741fc4fd2c 100644 --- a/src/sql/parts/query/editor/charting/insights/interfaces.ts +++ b/src/sql/parts/query/editor/charting/insights/interfaces.ts @@ -4,11 +4,10 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; +import { Dimension } from 'vs/base/browser/dom'; import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces'; -import { ChartType, LegendPosition, DataDirection } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component'; -import { Dimension } from 'vs/base/browser/dom'; -import { DataType } from 'sql/parts/dashboard/widgets/insights/views/charts/types/lineChart.component'; +import { DataDirection, ChartType, LegendPosition, DataType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; export interface IInsightOptions { type: InsightType | ChartType; diff --git a/src/sql/parts/query/editor/gridPanel.ts b/src/sql/parts/query/editor/gridPanel.ts index 2d6c0161da..f446b8891b 100644 --- a/src/sql/parts/query/editor/gridPanel.ts +++ b/src/sql/parts/query/editor/gridPanel.ts @@ -55,6 +55,11 @@ const ACTIONBAR_HEIGHT = 100; // this handles min size if rows is greater than the min grid visible rows const MIN_GRID_HEIGHT = (MIN_GRID_HEIGHT_ROWS * ROW_HEIGHT) + HEADER_HEIGHT + ESTIMATED_SCROLL_BAR_HEIGHT; +export class GridPanelState { + public tableStates: GridTableState[] = []; + public scrollPosition: number; + public collapsed = false; +} export interface IGridTableState { canBeMaximized: boolean; @@ -73,11 +78,12 @@ export class GridTableState { private _canBeMaximized: boolean; - constructor(state?: IGridTableState) { - if (state) { - this._maximized = state.maximized; - this._canBeMaximized = state.canBeMaximized; - } + /* The top row of the current scroll */ + public scrollPosition = 0; + public selection: Slick.Range[]; + public activeCell: Slick.Cell; + + constructor(public readonly resultId: number, public readonly batchId: number) { } public get canBeMaximized(): boolean { @@ -103,10 +109,6 @@ export class GridTableState { this._maximized = val; this._onMaximizedChange.fire(val); } - - public clone(): GridTableState { - return new GridTableState({ canBeMaximized: this.canBeMaximized, maximized: this.maximized }); - } } export class GridPanel extends ViewletPanel { @@ -120,6 +122,7 @@ export class GridPanel extends ViewletPanel { private runner: QueryRunner; private maximizedGrid: GridTable; + private _state: GridPanelState; constructor( options: IViewletPanelOptions, @@ -131,6 +134,16 @@ export class GridPanel extends ViewletPanel { ) { super(options, keybindingService, contextMenuService, configurationService); this.splitView = new ScrollableSplitView(this.container, { enableResizing: false }); + this.splitView.onScroll(e => { + if (this.state) { + this.state.scrollPosition = e; + } + }); + this.onDidChange(e => { + if (this.state) { + this.state.collapsed = !this.isExpanded(); + } + }); } protected renderBody(container: HTMLElement): void { @@ -168,6 +181,10 @@ export class GridPanel extends ViewletPanel { this.maximumBodySize = this.tables.reduce((p, c) => { return p + c.maximumSize; }, 0); + + if (this.state && this.state.scrollPosition) { + this.splitView.setScrollPosition(this.state.scrollPosition); + } } private addResultSet(resultSet: sqlops.ResultSetSummary | sqlops.ResultSetSummary[]) { @@ -181,8 +198,18 @@ export class GridPanel extends ViewletPanel { let tables: GridTable[] = []; for (let set of resultsToAdd) { - let tableState = new GridTableState(); - let table = this.instantiationService.createInstance(GridTable, this.runner, tableState, set); + let tableState: GridTableState; + if (this._state) { + tableState = this.state.tableStates.find(e => e.batchId === set.batchId && e.resultId === set.id); + } + if (!tableState) { + tableState = new GridTableState(set.id, set.batchId); + if (this._state) { + this._state.tableStates.push(tableState); + } + } + let table = this.instantiationService.createInstance(GridTable, this.runner, set); + table.state = tableState; tableState.onMaximizedChange(e => { if (e) { this.maximizeTable(table.id); @@ -241,6 +268,24 @@ export class GridPanel extends ViewletPanel { this.splitView.addViews(this.tables, this.tables.map(i => i.minimumSize)); } } + + public set state(val: GridPanelState) { + this._state = val; + this.tables.map(t => { + let state = this.state.tableStates.find(s => s.batchId === t.resultSet.batchId && s.resultId === t.resultSet.id); + if (!state) { + this.state.tableStates.push(t.state); + } + if (state) { + t.state = state; + } + }); + this.setExpanded(!this.state.collapsed); + } + + public get state(): GridPanelState { + return this._state; + } } class GridTable extends Disposable implements IView { @@ -259,13 +304,14 @@ class GridTable extends Disposable implements IView { public id = generateUuid(); readonly element: HTMLElement = this.container; + private _state: GridTableState; + // this handles if the row count is small, like 4-5 rows private readonly maxSize = ((this.resultSet.rowCount) * ROW_HEIGHT) + HEADER_HEIGHT + ESTIMATED_SCROLL_BAR_HEIGHT; constructor( private runner: QueryRunner, - public state: GridTableState, - private resultSet: sqlops.ResultSetSummary, + public readonly resultSet: sqlops.ResultSetSummary, @IContextMenuService private contextMenuService: IContextMenuService, @IInstantiationService private instantiationService: IInstantiationService, @IEditorService private editorService: IEditorService, @@ -357,10 +403,52 @@ class GridTable extends Disposable implements IView { }); this.actionBar.push(actions, { icon: true, label: false }); + this.selectionModel.onSelectedRangesChanged.subscribe(e => { + if (this.state) { + this.state.selection = this.selectionModel.getSelectedRanges(); + } + }); + + this.table.grid.onScroll.subscribe(e => { + if (this.state) { + this.state.scrollPosition = this.table.grid.getViewport().top; + } + }); + + this.table.grid.onActiveCellChanged.subscribe(e => { + if (this.state) { + this.state.activeCell = this.table.grid.getActiveCell(); + } + }); + + this.setupState(); + } + + private setupState() { // change actionbar on maximize change this.state.onMaximizedChange(this.rebuildActionBar, this); this.state.onCanBeMaximizedChange(this.rebuildActionBar, this); + + if (this.state.scrollPosition) { + this.table.grid.scrollRowToTop(this.state.scrollPosition); + } + + if (this.state.selection) { + this.selectionModel.setSelectedRanges(this.state.selection); + } + + if (this.state.activeCell) { + this.table.setActiveCell(this.state.activeCell.row, this.state.activeCell.cell); + } + } + + public get state(): GridTableState { + return this._state; + } + + public set state(val: GridTableState) { + this._state = val; } private onTableClick(event: ITableMouseEvent) { diff --git a/src/sql/parts/query/editor/messagePanel.ts b/src/sql/parts/query/editor/messagePanel.ts index d915a8f60f..000c501bf1 100644 --- a/src/sql/parts/query/editor/messagePanel.ts +++ b/src/sql/parts/query/editor/messagePanel.ts @@ -59,6 +59,11 @@ const TemplateIds = { ERROR: 'error' }; +export class MessagePanelState { + public scrollPosition: number; + public collapsed = false; +} + export class MessagePanel extends ViewletPanel { private ds = new MessageDataSource(); private renderer = new MessageRenderer(); @@ -67,6 +72,7 @@ export class MessagePanel extends ViewletPanel { private container = $('div message-tree').getHTMLElement(); private queryRunnerDisposables: IDisposable[] = []; + private _state: MessagePanelState; private tree: ITree; @@ -86,6 +92,16 @@ export class MessagePanel extends ViewletPanel { renderer: this.renderer, controller: this.controller }, { keyboardSupport: false }); + this.tree.onDidScroll(e => { + if (this.state) { + this.state.scrollPosition = this.tree.getScrollPosition(); + } + }); + this.onDidChange(e => { + if (this.state) { + this.state.collapsed = !this.isExpanded(); + } + }); } protected renderBody(container: HTMLElement): void { @@ -99,8 +115,12 @@ export class MessagePanel extends ViewletPanel { protected layoutBody(size: number): void { const previousScrollPosition = this.tree.getScrollPosition(); this.tree.layout(size); - if (previousScrollPosition === 1) { - this.tree.setScrollPosition(1); + if (this.state && this.state.scrollPosition) { + this.tree.setScrollPosition(this.state.scrollPosition); + } else { + if (previousScrollPosition === 1) { + this.tree.setScrollPosition(1); + } } } @@ -124,12 +144,19 @@ export class MessagePanel extends ViewletPanel { if (hasError) { this.setExpanded(true); } - const previousScrollPosition = this.tree.getScrollPosition(); - this.tree.refresh(this.model).then(() => { - if (previousScrollPosition === 1) { + if (this.state.scrollPosition) { + this.tree.refresh(this.model).then(() => { this.tree.setScrollPosition(1); - } - }); + }); + } else { + const previousScrollPosition = this.tree.getScrollPosition(); + this.tree.refresh(this.model).then(() => { + if (previousScrollPosition === 1) { + this.tree.setScrollPosition(1); + } + }); + } + this.maximumBodySize = this.model.messages.length * 22; } private reset() { @@ -137,6 +164,17 @@ export class MessagePanel extends ViewletPanel { this.model.totalExecuteMessage = undefined; this.tree.refresh(this.model); } + + public set state(val: MessagePanelState) { + this._state = val; + if (this.state.scrollPosition) { + this.tree.setScrollPosition(this.state.scrollPosition); + } + this.setExpanded(!this.state.collapsed); + } + public get state(): MessagePanelState { + return this._state; + } } class MessageDataSource implements IDataSource { diff --git a/src/sql/parts/query/editor/queryResultsView.ts b/src/sql/parts/query/editor/queryResultsView.ts index c3aabda4e0..53bcfbb14c 100644 --- a/src/sql/parts/query/editor/queryResultsView.ts +++ b/src/sql/parts/query/editor/queryResultsView.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput'; +import { QueryResultsInput, ResultsViewState } from 'sql/parts/query/common/queryResultsInput'; import { TabbedPanel, IPanelTab, IPanelView } from 'sql/base/browser/ui/panel/panel'; import { IQueryModelService } from '../execution/queryModel'; import QueryRunner from 'sql/parts/query/execution/queryRunner'; @@ -14,11 +14,10 @@ import { ChartTab } from './charting/chartTab'; import { QueryPlanTab } from 'sql/parts/queryPlan/queryPlan'; import * as nls from 'vs/nls'; -import * as UUID from 'vs/base/common/uuid'; import { PanelViewlet } from 'vs/workbench/browser/parts/views/panelViewlet'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import * as DOM from 'vs/base/browser/dom'; -import { once } from 'vs/base/common/event'; +import { once, anyEvent } from 'vs/base/common/event'; class ResultsView implements IPanelView { private panelViewlet: PanelViewlet; @@ -26,61 +25,72 @@ class ResultsView implements IPanelView { private messagePanel: MessagePanel; private container = document.createElement('div'); private currentDimension: DOM.Dimension; - private isGridRendered = false; private needsGridResize = false; - private lastGridHeight: number; + private _state: ResultsViewState; - constructor(instantiationService: IInstantiationService) { - this.panelViewlet = instantiationService.createInstance(PanelViewlet, 'resultsView', { showHeaderInTitleWhenSingleView: false }); - this.gridPanel = instantiationService.createInstance(GridPanel, { title: nls.localize('gridPanel', 'Results'), id: 'gridPanel' }); - this.messagePanel = instantiationService.createInstance(MessagePanel, { title: nls.localize('messagePanel', 'Messages'), minimumBodySize: 0, id: 'messagePanel' }); + constructor(private instantiationService: IInstantiationService) { + + this.panelViewlet = this.instantiationService.createInstance(PanelViewlet, 'resultsView', { showHeaderInTitleWhenSingleView: false }); + this.gridPanel = this.instantiationService.createInstance(GridPanel, { title: nls.localize('gridPanel', 'Results'), id: 'gridPanel' }); + this.messagePanel = this.instantiationService.createInstance(MessagePanel, { title: nls.localize('messagePanel', 'Messages'), minimumBodySize: 0, id: 'messagePanel' }); this.gridPanel.render(); this.messagePanel.render(); this.panelViewlet.create(this.container).then(() => { + this.gridPanel.setVisible(false); this.panelViewlet.addPanels([ { panel: this.messagePanel, size: this.messagePanel.minimumSize, index: 1 } ]); }); - this.gridPanel.onDidChange(e => { + anyEvent(this.gridPanel.onDidChange, this.messagePanel.onDidChange)(e => { let size = this.gridPanel.maximumBodySize; - if (this.isGridRendered) { - if (size < 1) { - this.lastGridHeight = this.panelViewlet.getPanelSize(this.gridPanel); - this.panelViewlet.removePanels([this.gridPanel]); - // tell the panel is has been removed. - this.gridPanel.layout(0); - this.isGridRendered = false; - } - } else { - if (this.currentDimension) { - this.needsGridResize = false; - if (size > 0) { - this.panelViewlet.addPanels([ - { panel: this.gridPanel, index: 0, size: this.lastGridHeight || Math.round(this.currentDimension.height * .7) } - ]); - this.isGridRendered = true; - } + if (size < 1 && this.gridPanel.isVisible()) { + this.gridPanel.setVisible(false); + this.panelViewlet.removePanels([this.gridPanel]); + this.gridPanel.layout(0); + } else if (size > 0 && !this.gridPanel.isVisible()) { + this.gridPanel.setVisible(true); + let panelSize: number; + if (this.state && this.state.gridPanelSize) { + panelSize = this.state.gridPanelSize; + } else if (this.currentDimension) { + panelSize = Math.round(this.currentDimension.height * .7); } else { - this.panelViewlet.addPanels([ - { panel: this.gridPanel, index: 0, size: this.lastGridHeight || 200 } - ]); - this.isGridRendered = true; + panelSize = 200; this.needsGridResize = true; } + this.panelViewlet.addPanels([{ panel: this.gridPanel, index: 0, size: panelSize }]); } }); - let gridResizeList = this.gridPanel.onDidChange(e => { - if (this.currentDimension) { - this.needsGridResize = false; - this.panelViewlet.resizePanel(this.gridPanel, Math.round(this.currentDimension.height * .7)); + let resizeList = anyEvent(this.gridPanel.onDidChange, this.messagePanel.onDidChange)(() => { + let panelSize: number; + if (this.state && this.state.gridPanelSize) { + panelSize = this.state.gridPanelSize; + } else if (this.currentDimension) { + panelSize = Math.round(this.currentDimension.height * .7); } else { + panelSize = 200; this.needsGridResize = true; } - }); + if (this.state.messagePanelSize) { + 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; - gridResizeList.dispose(); + resizeList.dispose(); + }); + + this.panelViewlet.onDidSashChange(e => { + if (this.state) { + if (this.gridPanel.isExpanded()) { + this.state.gridPanelSize = this.panelViewlet.getPanelSize(this.gridPanel); + } + if (this.messagePanel.isExpanded()) { + this.state.messagePanelSize = this.panelViewlet.getPanelSize(this.messagePanel); + } + } }); } @@ -96,7 +106,7 @@ class ResultsView implements IPanelView { } this.currentDimension = dimension; if (this.needsGridResize) { - this.panelViewlet.resizePanel(this.gridPanel, Math.round(this.currentDimension.height * .7)); + this.panelViewlet.resizePanel(this.gridPanel, this.state.gridPanelSize || Math.round(this.currentDimension.height * .7)); } } @@ -112,11 +122,21 @@ class ResultsView implements IPanelView { public hideResultHeader() { this.gridPanel.headerVisible = false; } + + public set state(val: ResultsViewState) { + this._state = val; + this.gridPanel.state = val.gridPanelState; + this.messagePanel.state = val.messagePanelState; + } + + public get state(): ResultsViewState { + return this._state; + } } class ResultsTab implements IPanelTab { public readonly title = nls.localize('resultsTabTitle', 'Results'); - public readonly identifier = UUID.generateUuid(); + public readonly identifier = 'resultsTab'; public readonly view: ResultsView; constructor(instantiationService: IInstantiationService) { @@ -144,6 +164,12 @@ export class QueryResultsView { this.chartTab = new ChartTab(instantiationService); this._panelView = new TabbedPanel(container, { showHeaderWhenSingleView: false }); this.qpTab = new QueryPlanTab(); + this._panelView.pushTab(this.resultsTab); + this._panelView.onTabChange(e => { + if (this.input) { + this.input.state.activeTab = e; + } + }); } public style() { @@ -151,11 +177,24 @@ export class QueryResultsView { public set input(input: QueryResultsInput) { this._input = input; + this.resultsTab.view.state = this.input.state; + this.qpTab.view.state = this.input.state.queryPlanState; + this.chartTab.view.state = this.input.state.chartState; let queryRunner = this.queryModelService._getQueryInfo(input.uri).queryRunner; this.resultsTab.queryRunner = queryRunner; this.chartTab.queryRunner = queryRunner; - if (!this._panelView.contains(this.resultsTab)) { - this._panelView.pushTab(this.resultsTab); + if (this.input.state.visibleTabs.has(this.chartTab.identifier)) { + if (!this._panelView.contains(this.chartTab)) { + this._panelView.pushTab(this.chartTab); + } + } + if (this.input.state.visibleTabs.has(this.qpTab.identifier)) { + if (!this._panelView.contains(this.qpTab)) { + this._panelView.pushTab(this.qpTab); + } + } + if (this.input.state.activeTab) { + this._panelView.showTab(this.input.state.activeTab); } } @@ -172,6 +211,7 @@ export class QueryResultsView { } public chartData(dataId: { resultId: number, batchId: number }): void { + this.input.state.visibleTabs.add(this.chartTab.identifier); if (!this._panelView.contains(this.chartTab)) { this._panelView.pushTab(this.chartTab); } @@ -181,6 +221,7 @@ export class QueryResultsView { } public showPlan(xml: string) { + this.input.state.visibleTabs.add(this.qpTab.identifier); if (!this._panelView.contains(this.qpTab)) { this._panelView.pushTab(this.qpTab); } diff --git a/src/sql/parts/queryPlan/queryPlan.ts b/src/sql/parts/queryPlan/queryPlan.ts index eb15477765..b80493a536 100644 --- a/src/sql/parts/queryPlan/queryPlan.ts +++ b/src/sql/parts/queryPlan/queryPlan.ts @@ -13,9 +13,13 @@ import { localize } from 'vs/nls'; import * as UUID from 'vs/base/common/uuid'; import { Builder } from 'vs/base/browser/builder'; +export class QueryPlanState { + xml: string; +} + export class QueryPlanTab implements IPanelTab { public readonly title = localize('queryPlanTitle', 'Query Plan'); - public readonly identifier = UUID.generateUuid(); + public readonly identifier = 'QueryPlanTab'; public readonly view: QueryPlanView; constructor() { @@ -27,6 +31,7 @@ export class QueryPlanView implements IPanelView { private qp: QueryPlan; private xml: string; private container = document.createElement('div'); + private _state: QueryPlanState; public render(container: HTMLElement): void { if (!this.qp) { @@ -47,6 +52,20 @@ export class QueryPlanView implements IPanelView { } else { this.xml = xml; } + if (this.state) { + this.state.xml = xml; + } + } + + public set state(val: QueryPlanState) { + this._state = val; + if (this.state.xml) { + this.showPlan(this.state.xml); + } + } + + public get state(): QueryPlanState { + return this._state; } } diff --git a/test/all.js b/test/all.js index fa0993af47..bd19a35a9c 100644 --- a/test/all.js +++ b/test/all.js @@ -61,7 +61,7 @@ function main() { 'bootstrap': `../${ out }/bootstrap` }, catchError: true, - // {{SQL CARBON EDIT}} + // {{SQL CARBON EDIT}} nodeModules: [ '@angular/common', '@angular/core', @@ -70,6 +70,7 @@ function main() { '@angular/platform-browser-dynamic', '@angular/router', 'angular2-grid', + 'ng2-charts/ng2-charts', 'rxjs/add/observable/of', 'rxjs/Observable', 'rxjs/Subject',