Compare commits

...

8 Commits

Author SHA1 Message Date
Matt Irvine
05040425df Add OE node refresh API method (#2578)
* Initial working commit for refreshing OE node via API

* Add test and fix up code

* Run tsfmt

* Fix test
2018-09-13 18:43:47 -07:00
Alan Ren
36f7c283b8 use latest slickgrid library (#2584) 2018-09-13 18:43:06 -07:00
Anthony Dresser
9fe4237033 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
2018-09-13 18:42:29 -07:00
Alan Ren
b03c0a3e2d accessibility setting based select database dropdown (#2579) 2018-09-13 15:07:08 -07:00
Aditya Bist
87946996ed added context to chart buttons so they work (#2575) 2018-09-13 14:22:57 -07:00
AlexFsmn
cc55023440 Disabled connection name input when connecting to a server. (#2566)
* Disabled connection name input when connecting to a server.
#2557

* Fixed reset state of connection inputs
2018-09-13 12:36:28 -07:00
Karl Burtram
1f19dfc50d Update SQL Ops to 0.33.5 2018-09-13 08:06:27 -07:00
Alan Ren
950a440350 fix the connection issue when opening new query after connection (#2561) 2018-09-13 08:05:46 -07:00
42 changed files with 617 additions and 190 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "sqlops",
"version": "0.33.4",
"version": "0.33.5",
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
"author": {
"name": "Microsoft Corporation"
@@ -64,7 +64,7 @@
"reflect-metadata": "^0.1.8",
"rxjs": "5.4.0",
"semver": "^5.5.0",
"slickgrid": "github:anthonydresser/SlickGrid#2.3.25",
"slickgrid": "github:anthonydresser/SlickGrid#2.3.27",
"spdlog": "0.7.1",
"sudo-prompt": "8.2.0",
"svg.js": "^2.2.5",

View File

@@ -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 {

View File

@@ -109,6 +109,9 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
private _onDidSashReset = new Emitter<void>();
readonly onDidSashReset = this._onDidSashReset.event;
private _onScroll = new Emitter<number>();
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;

View File

@@ -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<T> implements ICellRangeSelector<T> {
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);
}

View File

@@ -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;

View File

@@ -123,6 +123,7 @@ export class ConnectionStore {
.then(savedCred => {
if (savedCred) {
credentialsItem.password = savedCred.password;
credentialsItem.options['password'] = savedCred.password;
}
resolve({ profile: credentialsItem, savedCred: !!savedCred });
},

View File

@@ -492,6 +492,7 @@ export class ConnectionWidget {
this._databaseNameInputBox.enabled = false;
this._userNameInputBox.disable();
this._passwordInputBox.disable();
this._connectionNameInputBox.disable();
this._rememberPasswordCheckBox.enabled = false;
if (this._authTypeSelectBox) {
this._authTypeSelectBox.disable();
@@ -503,6 +504,7 @@ export class ConnectionWidget {
this._serverGroupSelectBox.enable();
this._serverNameInputBox.enable();
this._connectionNameInputBox.enable();
this._databaseNameInputBox.enabled = true;
let currentAuthType: AuthenticationType = undefined;
if (this._authTypeSelectBox) {

View File

@@ -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);

View File

@@ -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'
}

View File

@@ -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';

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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';

View File

@@ -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';

View File

@@ -72,6 +72,8 @@ export interface IObjectExplorerService {
getActiveConnectionNodes(): TreeNode[];
getTreeNode(connectionId: string, nodePath: string): Thenable<TreeNode>;
refreshNodeInView(connectionId: string, nodePath: string): Thenable<TreeNode>;
}
interface SessionStatus {
@@ -476,6 +478,20 @@ export class ObjectExplorerService implements IObjectExplorerService {
return Object.values(this._activeObjectExplorerNodes);
}
public async refreshNodeInView(connectionId: string, nodePath: string): Promise<TreeNode> {
// Get the tree node and call refresh from the provider
let treeNode = await this.getTreeNode(connectionId, nodePath);
await this.refreshTreeNode(treeNode.getSession(), treeNode);
// Get the new tree node, refresh it in the view, and expand it if needed
treeNode = await this.getTreeNode(connectionId, nodePath);
await this._serverTreeView.refreshElement(treeNode);
if (treeNode.children.length > 0) {
await treeNode.setExpandedState(TreeItemCollapsibleState.Expanded);
}
return treeNode;
}
private async setNodeExpandedState(treeNode: TreeNode, expandedState: TreeItemCollapsibleState): Promise<void> {
treeNode = await this.getUpdatedTreeNode(treeNode);
let expandNode = this.getTreeItem(treeNode);

View File

@@ -138,7 +138,7 @@ export class ServerTreeView {
let expandGroups: boolean = self._configurationService.getValue(SERVER_GROUP_CONFIG)[SERVER_GROUP_AUTOEXPAND_CONFIG];
if (expandGroups) {
self._tree.expandAll(ConnectionProfileGroup.getSubgroups(root));
self._tree.expandAll(ConnectionProfileGroup.getSubgroups(root));
}
if (root && !root.hasValidConnections) {
@@ -244,6 +244,10 @@ export class ServerTreeView {
TreeUpdateUtils.registeredServerUpdate(this._tree, this._connectionManagementService);
}
public refreshElement(element: any): Thenable<void> {
return this._tree.refresh(element);
}
/**
* Filter connections based on view (recent/active)
*/

View File

@@ -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<string> = new Set<string>();
}
/**
* 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<void>();
public readonly onSaveViewStateEmitter = new Emitter<void>();
public readonly state = new ResultsViewState();
constructor(private _uri: string) {
super();
this._visible = false;

View File

@@ -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<IInsightRegistry>(Extensions.InsightContribution);

View File

@@ -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) {

View File

@@ -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 = <IChartActionContext>{ 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();
}
@@ -172,7 +187,7 @@ export class ChartView implements IPanelView {
});
}
}
// if we have the necessary information but the information isn't avaiable yet,
// if we have the necessary information but the information isn't available yet,
// we should be smart and retrying when the information might be available
}
}
@@ -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();
}
}
}
@@ -211,6 +228,7 @@ export class ChartView implements IPanelView {
private updateActionbar() {
if (this.insight && this.insight.isCopyable) {
this.taskbar.context = { insight: this.insight.insight, options: this.options };
this.taskbar.setContent([
{ action: this._createInsightAction },
{ action: this._copyAction },
@@ -226,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;
}
}
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;
}
}

View File

@@ -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];

View File

@@ -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 = {

View File

@@ -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;

View File

@@ -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<any>;
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<any>[] = [];
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<T> extends Disposable implements IView {
@@ -259,13 +304,14 @@ class GridTable<T> 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<T> 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) {

View File

@@ -0,0 +1,5 @@
.monaco-select-box {
cursor: pointer;
min-width: 150px;
padding: 2px;
}

View File

@@ -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 {

View File

@@ -43,7 +43,6 @@ import {
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
import { IEditorDescriptorService } from 'sql/parts/query/editor/editorDescriptorService';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import { attachEditableDropdownStyler } from 'sql/common/theme/styler';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
@@ -500,7 +499,7 @@ export class QueryEditor extends BaseEditor {
public get listDatabasesActionItem(): ListDatabasesActionItem {
if (!this._listDatabasesActionItem) {
this._listDatabasesActionItem = this._instantiationService.createInstance(ListDatabasesActionItem, this, this._listDatabasesAction);
this._register(attachEditableDropdownStyler(this._listDatabasesActionItem, this.themeService));
this._register(this._listDatabasesActionItem.attachStyler(this.themeService));
}
return this._listDatabasesActionItem;
}

View File

@@ -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);
}

View File

@@ -3,6 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!sql/parts/query/editor/media/queryActions';
import * as nls from 'vs/nls';
import { Builder, $ } from 'vs/base/browser/builder';
import { Dropdown } from 'sql/base/browser/ui/editableDropdown/dropdown';
@@ -12,6 +13,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { attachEditableDropdownStyler, attachSelectBoxStyler } from 'sql/common/theme/styler';
import { ISelectionData } from 'sqlops';
import {
@@ -25,6 +27,8 @@ import { QueryEditor } from 'sql/parts/query/editor/queryEditor';
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
import { INotificationService } from 'vs/platform/notification/common/notification';
import Severity from 'vs/base/common/severity';
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
/**
* Action class that query-based Actions will extend. This base class automatically handles activating and
@@ -431,6 +435,9 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem
private _isConnected: boolean;
private $databaseListDropdown: Builder;
private _dropdown: Dropdown;
private _databaseSelectBox: SelectBox;
private _isInAccessibilityMode: boolean;
private readonly _selectDatabaseString: string = nls.localize("selectDatabase", "Select Database");
// CONSTRUCTOR /////////////////////////////////////////////////////////
constructor(
@@ -439,23 +446,33 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@INotificationService private _notificationService: INotificationService,
@IContextViewService contextViewProvider: IContextViewService,
@IThemeService themeService: IThemeService
@IThemeService themeService: IThemeService,
@IConfigurationService private readonly _configurationService: IConfigurationService
) {
super();
this._toDispose = [];
this.$databaseListDropdown = $('.databaseListDropdown');
let selectString = nls.localize("selectDatabase", "Select Database");
this._dropdown = new Dropdown(this.$databaseListDropdown.getHTMLElement(), contextViewProvider, themeService, {
strictSelection: true,
placeholder: selectString,
ariaLabel: selectString,
actionLabel: nls.localize('listDatabases.toggleDatabaseNameDropdown', 'Select Database Toggle Dropdown')
});
this._dropdown.onValueChange(s => this.databaseSelected(s));
this._isInAccessibilityMode = this._configurationService.getValue('editor.accessibilitySupport') === 'on';
if (this._isInAccessibilityMode) {
this._databaseSelectBox = new SelectBox([this._selectDatabaseString], this._selectDatabaseString, contextViewProvider, undefined, { ariaLabel: this._selectDatabaseString });
this._databaseSelectBox.render(this.$databaseListDropdown.getHTMLElement());
this._databaseSelectBox.onDidSelect(e => { this.databaseSelected(e.selected); });
this._databaseSelectBox.disable();
} else {
this._dropdown = new Dropdown(this.$databaseListDropdown.getHTMLElement(), contextViewProvider, themeService, {
strictSelection: true,
placeholder: this._selectDatabaseString,
ariaLabel: this._selectDatabaseString,
actionLabel: nls.localize('listDatabases.toggleDatabaseNameDropdown', 'Select Database Toggle Dropdown')
});
this._dropdown.onValueChange(s => this.databaseSelected(s));
this._toDispose.push(this._dropdown.onFocus(() => { self.onDropdownFocus(); }));
}
// Register event handlers
let self = this;
this._toDispose.push(this._dropdown.onFocus(() => { self.onDropdownFocus(); }));
this._toDispose.push(this._connectionManagementService.onConnectionChanged(params => { self.onConnectionChanged(params); }));
}
@@ -465,7 +482,12 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem
}
public style(styles) {
this._dropdown.style(styles);
if (this._isInAccessibilityMode) {
this._databaseSelectBox.style(styles);
}
else {
this._dropdown.style(styles);
}
}
public setActionContext(context: any): void {
@@ -477,11 +499,27 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem
}
public focus(): void {
this._dropdown.focus();
if (this._isInAccessibilityMode) {
this._databaseSelectBox.focus();
} else {
this._dropdown.focus();
}
}
public blur(): void {
this._dropdown.blur();
if (this._isInAccessibilityMode) {
this._databaseSelectBox.blur();
} else {
this._dropdown.blur();
}
}
public attachStyler(themeService: IThemeService): IDisposable {
if (this._isInAccessibilityMode) {
return attachSelectBoxStyler(this, themeService);
} else {
return attachEditableDropdownStyler(this, themeService);
}
}
public dispose(): void {
@@ -496,9 +534,15 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem
public onDisconnect(): void {
this._isConnected = false;
this._dropdown.enabled = false;
this._currentDatabaseName = undefined;
this._dropdown.value = '';
if (this._isInAccessibilityMode) {
this._databaseSelectBox.disable();
this._databaseSelectBox.setOptions([this._selectDatabaseString]);
} else {
this._dropdown.enabled = false;
this._dropdown.value = '';
}
}
// PRIVATE HELPERS /////////////////////////////////////////////////////
@@ -515,22 +559,22 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem
this._connectionManagementService.changeDatabase(this._editor.uri, dbName)
.then(
result => {
if (!result) {
result => {
if (!result) {
this.resetDatabaseName();
this._notificationService.notify({
severity: Severity.Error,
message: nls.localize('changeDatabase.failed', "Failed to change database")
});
}
},
error => {
this.resetDatabaseName();
this._notificationService.notify({
severity: Severity.Error,
message: nls.localize('changeDatabase.failed', "Failed to change database")
message: nls.localize('changeDatabase.failedWithError', "Failed to change database {0}", error)
});
}
},
error => {
this.resetDatabaseName();
this._notificationService.notify({
severity: Severity.Error,
message: nls.localize('changeDatabase.failedWithError', "Failed to change database {0}", error)
});
});
}
private getCurrentDatabaseName() {
@@ -545,7 +589,11 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem
}
private resetDatabaseName() {
this._dropdown.value = this.getCurrentDatabaseName();
if (this._isInAccessibilityMode) {
this._databaseSelectBox.selectWithOptionName(this.getCurrentDatabaseName());
} else {
this._dropdown.value = this.getCurrentDatabaseName();
}
}
private onConnectionChanged(connParams: IConnectionParams): void {
@@ -579,9 +627,26 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem
private updateConnection(databaseName: string) {
this._isConnected = true;
this._dropdown.enabled = true;
this._currentDatabaseName = databaseName;
this._dropdown.value = databaseName;
if (this._isInAccessibilityMode) {
this._databaseSelectBox.enable();
let self = this;
let uri = self._editor.connectedUri;
if (!uri) {
return;
}
self._connectionManagementService.listDatabases(uri)
.then(result => {
if (result && result.databaseNames) {
this._databaseSelectBox.setOptions(result.databaseNames);
}
this._databaseSelectBox.selectWithOptionName(databaseName);
});
} else {
this._dropdown.enabled = true;
this._dropdown.value = databaseName;
}
}
// TESTING PROPERTIES //////////////////////////////////////////////////

View File

@@ -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;
}
}

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

@@ -185,6 +185,11 @@ declare module 'sqlops' {
* Get the parent node. Returns undefined if there is none.
*/
getParent(): Thenable<ObjectExplorerNode>;
/**
* Refresh the node, expanding it if it has children
*/
refresh(): Thenable<void>;
}
}

View File

@@ -9,7 +9,7 @@ import { ExtHostObjectExplorerShape, SqlMainContext, MainThreadObjectExplorerSha
import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
export class ExtHostObjectExplorer implements ExtHostObjectExplorerShape {
export class ExtHostObjectExplorer implements ExtHostObjectExplorerShape {
private _proxy: MainThreadObjectExplorerShape;
@@ -44,7 +44,7 @@ class ExtHostObjectExplorerNode implements sqlops.objectexplorer.ObjectExplorerN
public errorMessage: string;
constructor(nodeInfo: sqlops.NodeInfo, connectionId: string, private _proxy: MainThreadObjectExplorerShape) {
Object.entries(nodeInfo).forEach(([key, value]) => this[key] = value);
this.getDetailsFromInfo(nodeInfo);
this.connectionId = connectionId;
}
@@ -71,4 +71,12 @@ class ExtHostObjectExplorerNode implements sqlops.objectexplorer.ObjectExplorerN
}
return this._proxy.$getNode(this.connectionId, this.nodePath.slice(0, parentPathEndIndex)).then(nodeInfo => nodeInfo ? new ExtHostObjectExplorerNode(nodeInfo, this.connectionId, this._proxy) : undefined);
}
refresh(): Thenable<void> {
return this._proxy.$refresh(this.connectionId, this.nodePath).then(nodeInfo => this.getDetailsFromInfo(nodeInfo));
}
private getDetailsFromInfo(nodeInfo: sqlops.NodeInfo): void {
Object.entries(nodeInfo).forEach(([key, value]) => this[key] = value);
}
}

View File

@@ -15,6 +15,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { TreeItemCollapsibleState } from 'sql/parts/objectExplorer/common/treeNode';
@extHostNamedCustomer(SqlMainContext.MainThreadObjectExplorer)
export class MainThreadObjectExplorer implements MainThreadObjectExplorerShape {
@@ -50,7 +51,7 @@ export class MainThreadObjectExplorer implements MainThreadObjectExplorerShape {
public $getActiveConnectionNodes(): Thenable<NodeInfoWithConnection[]> {
let connectionNodes = this._objectExplorerService.getActiveConnectionNodes();
return Promise.resolve(connectionNodes.map(node => {
return {connectionId: node.connection.id, nodeInfo: node.toNodeInfo()};
return { connectionId: node.connection.id, nodeInfo: node.toNodeInfo() };
}));
}
@@ -73,4 +74,8 @@ export class MainThreadObjectExplorer implements MainThreadObjectExplorerShape {
public $findNodes(connectionId: string, type: string, schema: string, name: string, database: string, parentObjectNames: string[]): Thenable<sqlops.NodeInfo[]> {
return this._objectExplorerService.findNodes(connectionId, type, schema, name, database, parentObjectNames);
}
public $refresh(connectionId: string, nodePath: string): Thenable<sqlops.NodeInfo> {
return this._objectExplorerService.refreshNodeInView(connectionId, nodePath).then(node => node.toNodeInfo());
}
}

View File

@@ -36,7 +36,7 @@ export abstract class ExtHostAccountManagementShape {
export abstract class ExtHostConnectionManagementShape {
$onConnectionOpened(handleId: string, connection: sqlops.connection.Connection): void { throw ni; }
}
}
export abstract class ExtHostDataProtocolShape {
@@ -663,6 +663,7 @@ export interface MainThreadObjectExplorerShape extends IDisposable {
$getChildren(connectionId: string, nodePath: string): Thenable<sqlops.NodeInfo[]>;
$isExpanded(connectionId: string, nodePath: string): Thenable<boolean>;
$findNodes(connectionId: string, type: string, schema: string, name: string, database: string, parentObjectNames: string[]): Thenable<sqlops.NodeInfo[]>;
$refresh(connectionId: string, nodePath: string): Thenable<sqlops.NodeInfo>;
}
export interface ExtHostModelViewDialogShape {

View File

@@ -301,7 +301,8 @@ suite('SQL Object Explorer Service tests', () => {
reveal: element => Promise.resolve() as Thenable<void>,
setSelected: (element, selected, clearOtherSelections) => undefined,
isExpanded: element => undefined,
onSelectionOrFocusChange: Event.None
onSelectionOrFocusChange: Event.None,
refreshElement: (element) => Promise.resolve() as Thenable<void>
} as ServerTreeView);
});
@@ -738,4 +739,22 @@ suite('SQL Object Explorer Service tests', () => {
}, err => done(err));
});
});
test('refreshInView refreshes the node, expands it, and returns the refreshed node', async () => {
// Set up the session and tree view
await objectExplorerService.createNewSession('MSSQL', connection);
objectExplorerService.onSessionCreated(1, objectExplorerSession);
serverTreeView.setup(x => x.refreshElement(TypeMoq.It.isAny())).returns(() => Promise.resolve());
objectExplorerService.registerServerTreeView(serverTreeView.object);
// Refresh the node
let nodePath = objectExplorerSession.rootNode.nodePath;
let refreshedNode = await objectExplorerService.refreshNodeInView(connection.id, nodePath);
// Verify that it was refreshed, expanded, and the refreshed detailed were returned
sqlOEProvider.verify(x => x.refreshNode(TypeMoq.It.is(refreshNode => refreshNode.nodePath === nodePath)), TypeMoq.Times.once());
refreshedNode.children.forEach((childNode, index) => {
assert.equal(childNode.nodePath, objectExplorerExpandInfoRefresh.nodes[index].nodePath);
});
});
});

View File

@@ -28,6 +28,7 @@ import { ConnectionManagementService } from 'sql/parts/connection/common/connect
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { TestThemeService } from 'sqltest/stubs/themeTestService';
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
import * as TypeMoq from 'typemoq';
import * as assert from 'assert';
@@ -40,6 +41,7 @@ suite('SQL QueryAction Tests', () => {
let editor: TypeMoq.Mock<QueryEditor>;
let calledRunQueryOnInput: boolean = undefined;
let testQueryInput: TypeMoq.Mock<QueryInput>;
let configurationService: TypeMoq.Mock<ConfigurationService>;
setup(() => {
// Setup a reusable mock QueryInput
@@ -56,6 +58,13 @@ suite('SQL QueryAction Tests', () => {
editor.setup(x => x.getSelection()).returns(() => undefined);
editor.setup(x => x.getSelection(false)).returns(() => undefined);
editor.setup(x => x.isSelectionEmpty()).returns(() => false);
configurationService = TypeMoq.Mock.ofInstance({
getValue: () => undefined,
onDidChangeConfiguration: () => undefined
} as any);
configurationService.setup(x => x.getValue(TypeMoq.It.isAny())).returns(() => {
return {};
});
});
test('setClass sets child CSS class correctly', (done) => {
@@ -463,7 +472,7 @@ suite('SQL QueryAction Tests', () => {
});
// If I query without having initialized anything, state should be clear
listItem = new ListDatabasesActionItem(editor.object, undefined, connectionManagementService.object, undefined, undefined, undefined);
listItem = new ListDatabasesActionItem(editor.object, undefined, connectionManagementService.object, undefined, undefined, undefined, configurationService.object);
assert.equal(listItem.isEnabled(), false, 'do not expect dropdown enabled unless connected');
assert.equal(listItem.currentDatabaseName, undefined, 'do not expect dropdown to have entries unless connected');
@@ -498,7 +507,7 @@ suite('SQL QueryAction Tests', () => {
cms.setup(x => x.getConnectionProfile(TypeMoq.It.isAny())).returns(() => <IConnectionProfile>{ databaseName: databaseName });
// ... Create a database dropdown that has been connected
let listItem = new ListDatabasesActionItem(editor.object, undefined, cms.object, null, null, null);
let listItem = new ListDatabasesActionItem(editor.object, undefined, cms.object, null, null, null, configurationService.object);
listItem.onConnected();
// If: I raise a connection changed event
@@ -522,7 +531,7 @@ suite('SQL QueryAction Tests', () => {
cms.setup(x => x.getConnectionProfile(TypeMoq.It.isAny())).returns(() => <IConnectionProfile>{ databaseName: databaseName });
// ... Create a database dropdown that has been connected
let listItem = new ListDatabasesActionItem(editor.object, undefined, cms.object, null, null, null);
let listItem = new ListDatabasesActionItem(editor.object, undefined, cms.object, null, null, null, configurationService.object);
listItem.onConnected();
// If: I raise a connection changed event for the 'wrong' URI
@@ -549,7 +558,7 @@ suite('SQL QueryAction Tests', () => {
cms.setup(x => x.onConnectionChanged).returns(() => dbChangedEmitter.event);
// ... Create a database dropdown
let listItem = new ListDatabasesActionItem(editor.object, undefined, cms.object, null, null, null);
let listItem = new ListDatabasesActionItem(editor.object, undefined, cms.object, null, null, null, configurationService.object);
// If: I raise a connection changed event
let eventParams = <IConnectionParams>{

View File

@@ -93,7 +93,7 @@ suite('SQL QueryEditor Tests', () => {
instantiationService.setup(x => x.createInstance(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((classDef, editor, action) => {
if (classDef.ID) {
if (classDef.ID === 'listDatabaseQueryActionItem') {
return new ListDatabasesActionItem(editor, action, connectionManagementService.object, undefined, undefined, undefined);
return new ListDatabasesActionItem(editor, action, connectionManagementService.object, undefined, undefined, undefined, configurationService.object);
}
}
// Default
@@ -344,7 +344,7 @@ suite('SQL QueryEditor Tests', () => {
queryActionInstantiationService.setup(x => x.createInstance(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()))
.returns((definition, editor, action, selectBox) => {
if (definition.ID === 'listDatabaseQueryActionItem') {
let item = new ListDatabasesActionItem(editor, action, queryConnectionService.object, undefined, undefined, undefined);
let item = new ListDatabasesActionItem(editor, action, queryConnectionService.object, undefined, undefined, undefined,configurationService.object);
return item;
}
// Default

View File

@@ -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',

View File

@@ -6111,9 +6111,9 @@ slice-ansi@0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
"slickgrid@github:anthonydresser/SlickGrid#2.3.25":
version "2.3.25"
resolved "https://codeload.github.com/anthonydresser/SlickGrid/tar.gz/5fa498a3df7ed671958bedeac1863c41352c7825"
"slickgrid@github:anthonydresser/SlickGrid#2.3.27":
version "2.3.27"
resolved "https://codeload.github.com/anthonydresser/SlickGrid/tar.gz/712a59307942c8fdb18708b6d1a501880a74db8d"
dependencies:
jquery ">=1.8.0"
jquery-ui ">=1.8.0"