Compare commits

...

6 Commits

Author SHA1 Message Date
Anthony Dresser
5d63905056 Rework timeSeries in chart viewer (#2987)
* rework timeSeries in chart viewer

* rework important to fix tests
2018-10-29 13:12:49 -07:00
Karl Burtram
cb224fbc74 Update Azure Data Studio to 1.1.4 2018-10-29 12:05:16 -07:00
Karl Burtram
067846099b Format JSON and XML output when clicking resultgrid link (#3024) 2018-10-29 12:04:00 -07:00
Karl Burtram
0e14908360 Merge 98d06b2892 2018-10-29 12:00:03 -07:00
Anthony Dresser
27735dd68b Clean up result tab better (#3015)
* do a better job cleaning up results tab

* formatting
2018-10-29 11:54:42 -07:00
Anthony Dresser
a8eaf28884 Fix time series (#2985)
* fix time series type string

* remove unused code
2018-10-29 11:54:32 -07:00
17 changed files with 282 additions and 126 deletions

View File

@@ -1,18 +1,18 @@
{ {
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}", "downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
"version": "1.5.0-alpha.43", "version": "1.5.0-alpha.48",
"downloadFileNames": { "downloadFileNames": {
"Windows_86": "win-x86-netcoreapp2.1.zip", "Windows_86": "win-x86-netcoreapp2.2.zip",
"Windows_64": "win-x64-netcoreapp2.1.zip", "Windows_64": "win-x64-netcoreapp2.2.zip",
"OSX": "osx-x64-netcoreapp2.1.tar.gz", "OSX": "osx-x64-netcoreapp2.2.tar.gz",
"CentOS_7": "rhel-x64-netcoreapp2.1.tar.gz", "CentOS_7": "rhel-x64-netcoreapp2.2.tar.gz",
"Debian_8": "rhel-x64-netcoreapp2.1.tar.gz", "Debian_8": "rhel-x64-netcoreapp2.2.tar.gz",
"Fedora_23": "rhel-x64-netcoreapp2.1.tar.gz", "Fedora_23": "rhel-x64-netcoreapp2.2.tar.gz",
"OpenSUSE_13_2": "rhel-x64-netcoreapp2.1.tar.gz", "OpenSUSE_13_2": "rhel-x64-netcoreapp2.2.tar.gz",
"RHEL_7": "rhel-x64-netcoreapp2.1.tar.gz", "RHEL_7": "rhel-x64-netcoreapp2.2.tar.gz",
"SLES_12_2": "rhel-x64-netcoreapp2.1.tar.gz", "SLES_12_2": "rhel-x64-netcoreapp2.2.tar.gz",
"Ubuntu_14": "rhel-x64-netcoreapp2.1.tar.gz", "Ubuntu_14": "rhel-x64-netcoreapp2.2.tar.gz",
"Ubuntu_16": "rhel-x64-netcoreapp2.1.tar.gz" "Ubuntu_16": "rhel-x64-netcoreapp2.2.tar.gz"
}, },
"installDirectory": "../sqltoolsservice/{#platform#}/{#version#}", "installDirectory": "../sqltoolsservice/{#platform#}/{#version#}",
"executableFiles": ["MicrosoftSqlToolsServiceLayer.exe", "MicrosoftSqlToolsServiceLayer"] "executableFiles": ["MicrosoftSqlToolsServiceLayer.exe", "MicrosoftSqlToolsServiceLayer"]

View File

@@ -1,6 +1,6 @@
{ {
"name": "azuredatastudio", "name": "azuredatastudio",
"version": "1.1.3", "version": "1.1.4",
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee", "distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
"author": { "author": {
"name": "Microsoft Corporation" "name": "Microsoft Corporation"

View File

@@ -63,7 +63,7 @@ export class TabbedPanel extends Disposable implements IThemable {
private tabHistory: string[] = []; private tabHistory: string[] = [];
constructor(private container: HTMLElement, private options: IPanelOptions = defaultOptions) { constructor(container: HTMLElement, private options: IPanelOptions = defaultOptions) {
super(); super();
this.parent = $('.tabbedPanel'); this.parent = $('.tabbedPanel');
container.appendChild(this.parent); container.appendChild(this.parent);
@@ -87,6 +87,13 @@ export class TabbedPanel extends Disposable implements IThemable {
this.parent.appendChild(this.body); this.parent.appendChild(this.body);
} }
public dispose() {
this.header.remove();
this.tabList.remove();
this.body.remove();
this.parent.remove();
}
public contains(tab: IPanelTab): boolean { public contains(tab: IPanelTab): boolean {
return this._tabMap.has(tab.identifier); return this._tabMap.has(tab.identifier);
} }

View File

@@ -10,10 +10,9 @@ import * as TelemetryUtils from 'sql/common/telemetryUtilities';
import { IInsightsView, IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces'; import { IInsightsView, IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces';
import { memoize, unmemoize } from 'sql/base/common/decorators'; import { memoize, unmemoize } from 'sql/base/common/decorators';
import { mixin } from 'sql/base/common/objects'; import { mixin } from 'sql/base/common/objects';
import { LegendPosition, DataDirection, ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; import { LegendPosition, ChartType, defaultChartConfig, IChartConfig, IDataSet, IPointDataSet } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
import * as colors from 'vs/platform/theme/common/colorRegistry'; import * as colors from 'vs/platform/theme/common/colorRegistry';
import { Color } from 'vs/base/common/color';
import * as types from 'vs/base/common/types'; import * as types from 'vs/base/common/types';
import { Disposable } from 'vs/base/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle';
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
@@ -22,51 +21,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
declare var Chart: any; declare var Chart: any;
export function customMixin(destination: any, source: any, overwrite?: boolean): any {
if (types.isObject(source)) {
mixin(destination, source, overwrite, customMixin);
} else if (types.isArray(source)) {
for (let i = 0; i < source.length; i++) {
if (destination[i]) {
mixin(destination[i], source[i], overwrite, customMixin);
} else {
destination[i] = source[i];
}
}
} else {
destination = source;
}
return destination;
}
export interface IDataSet {
data: Array<number>;
label?: string;
}
export interface IPointDataSet {
data: Array<{ x: number | string, y: number }>;
label?: string;
fill: boolean;
backgroundColor?: Color;
}
export interface IChartConfig {
colorMap?: { [column: string]: string };
labelFirstColumn?: boolean;
legendPosition?: LegendPosition;
dataDirection?: DataDirection;
columnsAsLabels?: boolean;
showTopNData?: number;
}
export const defaultChartConfig: IChartConfig = {
labelFirstColumn: true,
columnsAsLabels: true,
legendPosition: LegendPosition.Top,
dataDirection: DataDirection.Vertical
};
@Component({ @Component({
template: ` <div style="display: block; width: 100%; height: 100%; position: relative"> template: ` <div style="display: block; width: 100%; height: 100%; position: relative">
<canvas #canvas *ngIf="_isDataAvailable && _hasInit" <canvas #canvas *ngIf="_isDataAvailable && _hasInit"

View File

@@ -3,6 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { Color } from 'vs/base/common/color';
import * as types from 'vs/base/common/types';
import { mixin } from 'sql/base/common/objects';
export enum ChartType { export enum ChartType {
Bar = 'bar', Bar = 'bar',
Doughnut = 'doughnut', Doughnut = 'doughnut',
@@ -29,4 +34,49 @@ export enum LegendPosition {
export enum DataType { export enum DataType {
Number = 'number', Number = 'number',
Point = 'point' Point = 'point'
} }
export function customMixin(destination: any, source: any, overwrite?: boolean): any {
if (types.isObject(source)) {
mixin(destination, source, overwrite, customMixin);
} else if (types.isArray(source)) {
for (let i = 0; i < source.length; i++) {
if (destination[i]) {
mixin(destination[i], source[i], overwrite, customMixin);
} else {
destination[i] = source[i];
}
}
} else {
destination = source;
}
return destination;
}
export interface IDataSet {
data: Array<number>;
label?: string;
}
export interface IPointDataSet {
data: Array<{ x: number | string, y: number }>;
label?: string;
fill: boolean;
backgroundColor?: Color;
}
export interface IChartConfig {
colorMap?: { [column: string]: string };
labelFirstColumn?: boolean;
legendPosition?: LegendPosition;
dataDirection?: DataDirection;
columnsAsLabels?: boolean;
showTopNData?: number;
}
export const defaultChartConfig: IChartConfig = {
labelFirstColumn: true,
columnsAsLabels: true,
legendPosition: LegendPosition.Top,
dataDirection: DataDirection.Vertical
};

View File

@@ -3,9 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { ChartInsight, customMixin, IChartConfig } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component'; import { ChartInsight } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
import { mixin } from 'sql/base/common/objects'; import { mixin } from 'sql/base/common/objects';
import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; import { ChartType, IChartConfig, customMixin } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
import { IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
import * as colors from 'vs/platform/theme/common/colorRegistry'; import * as colors from 'vs/platform/theme/common/colorRegistry';

View File

@@ -5,11 +5,10 @@
import { mixin } from 'vs/base/common/objects'; 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 BarChart, { IBarChartConfig } from './barChart.component';
import { memoize, unmemoize } from 'sql/base/common/decorators'; import { memoize, unmemoize } from 'sql/base/common/decorators';
import { clone } from 'sql/base/common/objects'; import { clone } from 'sql/base/common/objects';
import { ChartType, DataType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; import { ChartType, DataType, defaultChartConfig, IDataSet, IPointDataSet } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
export interface ILineConfig extends IBarChartConfig { export interface ILineConfig extends IBarChartConfig {
dataType?: DataType; dataType?: DataType;

View File

@@ -3,10 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { defaultChartConfig } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
import LineChart, { ILineConfig } from './lineChart.component'; import LineChart, { ILineConfig } from './lineChart.component';
import { clone } from 'sql/base/common/objects'; import { clone } from 'sql/base/common/objects';
import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; import { ChartType, defaultChartConfig } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
import { mixin } from 'vs/base/common/objects'; import { mixin } from 'vs/base/common/objects';

View File

@@ -3,10 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { defaultChartConfig, IPointDataSet } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
import LineChart, { ILineConfig } from './lineChart.component'; import LineChart, { ILineConfig } from './lineChart.component';
import { clone } from 'sql/base/common/objects'; import { clone } from 'sql/base/common/objects';
import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; import { ChartType, defaultChartConfig, IPointDataSet } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
import { mixin } from 'vs/base/common/objects'; import { mixin } from 'vs/base/common/objects';
import { Color } from 'vs/base/common/color'; import { Color } from 'vs/base/common/color';

View File

@@ -18,7 +18,8 @@ export enum ControlType {
combo, combo,
numberInput, numberInput,
input, input,
checkbox checkbox,
dateInput
} }
export interface IChartOption { export interface IChartOption {
@@ -115,6 +116,20 @@ const xAxisMaxInput: IChartOption = {
default: undefined default: undefined
}; };
const xAxisMinDateInput: IChartOption = {
label: localize('xAxisMinDate', 'X Axis Minimum Date'),
type: ControlType.dateInput,
configEntry: 'xAxisMin',
default: undefined
};
const xAxisMaxDateInput: IChartOption = {
label: localize('xAxisMaxDate', 'X Axis Maximum Date'),
type: ControlType.dateInput,
configEntry: 'xAxisMax',
default: undefined
};
const dataTypeInput: IChartOption = { const dataTypeInput: IChartOption = {
label: localize('dataTypeLabel', 'Data Type'), label: localize('dataTypeLabel', 'Data Type'),
type: ControlType.combo, type: ControlType.combo,
@@ -150,7 +165,11 @@ export const ChartOptions: IChartOptions = {
[ChartType.TimeSeries]: [ [ChartType.TimeSeries]: [
legendInput, legendInput,
yAxisLabelInput, yAxisLabelInput,
xAxisLabelInput yAxisMinInput,
yAxisMaxInput,
xAxisLabelInput,
xAxisMinDateInput,
xAxisMaxDateInput,
], ],
[ChartType.Bar]: [ [ChartType.Bar]: [
dataDirectionOption, dataDirectionOption,

View File

@@ -18,7 +18,7 @@ export class ChartTab implements IPanelTab {
public readonly identifier = 'ChartTab'; public readonly identifier = 'ChartTab';
public readonly view: ChartView; public readonly view: ChartView;
constructor(@IInstantiationService instantiationService: IInstantiationService) { constructor( @IInstantiationService instantiationService: IInstantiationService) {
this.view = instantiationService.createInstance(ChartView); this.view = instantiationService.createInstance(ChartView);
} }
@@ -26,7 +26,11 @@ export class ChartTab implements IPanelTab {
this.view.queryRunner = runner; this.view.queryRunner = runner;
} }
public chart(dataId: { batchId: number, resultId: number}): void { public chart(dataId: { batchId: number, resultId: number }): void {
this.view.chart(dataId); this.view.chart(dataId);
} }
public dispose() {
this.view.dispose();
}
} }

View File

@@ -24,7 +24,7 @@ import { SelectBox } from 'vs/base/browser/ui/selectBox/selectBox';
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { Builder } from 'vs/base/browser/builder'; import { Builder } from 'vs/base/browser/builder';
import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
import { attachSelectBoxStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { attachSelectBoxStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -43,7 +43,7 @@ declare class Proxy {
const insightRegistry = Registry.as<IInsightRegistry>(Extensions.InsightContribution); const insightRegistry = Registry.as<IInsightRegistry>(Extensions.InsightContribution);
export class ChartView implements IPanelView { export class ChartView extends Disposable implements IPanelView {
private insight: Insight; private insight: Insight;
private _queryRunner: QueryRunner; private _queryRunner: QueryRunner;
private _data: IInsightData; private _data: IInsightData;
@@ -82,6 +82,7 @@ export class ChartView implements IPanelView {
@IInstantiationService private _instantiationService: IInstantiationService, @IInstantiationService private _instantiationService: IInstantiationService,
@IContextMenuService contextMenuService: IContextMenuService @IContextMenuService contextMenuService: IContextMenuService
) { ) {
super();
this.taskbarContainer = $('div.taskbar-container'); this.taskbarContainer = $('div.taskbar-container');
this.taskbar = new Taskbar(this.taskbarContainer, contextMenuService); this.taskbar = new Taskbar(this.taskbarContainer, contextMenuService);
this.optionsControl = $('div.options-container'); this.optionsControl = $('div.options-container');
@@ -324,6 +325,24 @@ export class ChartView implements IPanelView {
}; };
this.optionDisposables.push(attachInputBoxStyler(numberInput, this._themeService)); this.optionDisposables.push(attachInputBoxStyler(numberInput, this._themeService));
break; break;
case ControlType.dateInput:
let dateInput = new InputBox(optionContainer, this._contextViewService, { type: 'date' });
dateInput.value = value || '';
dateInput.onDidChange(e => {
if (this.options[option.configEntry] !== e) {
this.options[option.configEntry] = e;
if (this.insight) {
this.insight.options = this.options;
}
}
});
setFunc = (val: string) => {
if (!isUndefinedOrNull(val)) {
dateInput.value = val;
}
};
this.optionDisposables.push(attachInputBoxStyler(dateInput, this._themeService));
break;
} }
this.optionMap[option.configEntry] = { element: optionContainer, set: setFunc }; this.optionMap[option.configEntry] = { element: optionContainer, set: setFunc };
container.appendChild(optionContainer); container.appendChild(optionContainer);

View File

@@ -7,7 +7,7 @@
import { Chart as ChartJs } from 'chart.js'; import { Chart as ChartJs } from 'chart.js';
import { mixin } from 'vs/base/common/objects'; import { mixin } from 'sql/base/common/objects';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import * as colors from 'vs/platform/theme/common/colorRegistry'; import * as colors from 'vs/platform/theme/common/colorRegistry';
import { editorLineNumbers } from 'vs/editor/common/view/editorColorRegistry'; import { editorLineNumbers } from 'vs/editor/common/view/editorColorRegistry';
@@ -15,10 +15,28 @@ import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces'; import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces';
import { IInsightOptions, IInsight } from './interfaces'; import { IInsightOptions, IInsight } from './interfaces';
import { ChartType, DataDirection, LegendPosition } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; import { ChartType, DataDirection, LegendPosition, DataType, IPointDataSet, customMixin } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
const noneLineGraphs = [ChartType.Doughnut, ChartType.Pie]; const noneLineGraphs = [ChartType.Doughnut, ChartType.Pie];
const timeSeriesScales = {
scales: {
xAxes: [{
type: 'time',
display: true,
ticks: {
autoSkip: false,
maxRotation: 45,
minRotation: 45
}
}],
yAxes: [{
display: true,
}]
}
};
const defaultOptions: IInsightOptions = { const defaultOptions: IInsightOptions = {
type: ChartType.Bar, type: ChartType.Bar,
dataDirection: DataDirection.Horizontal dataDirection: DataDirection.Horizontal
@@ -30,6 +48,8 @@ export class Graph implements IInsight {
private chartjs: ChartJs; private chartjs: ChartJs;
private _data: IInsightData; private _data: IInsightData;
private originalType: ChartType;
public static readonly types = [ChartType.Bar, ChartType.Doughnut, ChartType.HorizontalBar, ChartType.Line, ChartType.Pie, ChartType.Scatter, ChartType.TimeSeries]; public static readonly types = [ChartType.Bar, ChartType.Doughnut, ChartType.HorizontalBar, ChartType.Line, ChartType.Pie, ChartType.Scatter, ChartType.TimeSeries];
public readonly types = Graph.types; public readonly types = Graph.types;
@@ -83,37 +103,51 @@ export class Graph implements IInsight {
labels = data.rows.map(row => row[0]); labels = data.rows.map(row => row[0]);
} }
if (this.options.dataDirection === DataDirection.Horizontal) { if (this.originalType === ChartType.TimeSeries) {
if (this.options.labelFirstColumn) { let dataSetMap: { [label: string]: IPointDataSet } = {};
chartData = data.rows.map((row) => { this._data.rows.map(row => {
return { if (row && row.length >= 3) {
data: row.map(item => Number(item)).slice(1), let legend = row[0];
label: row[0] if (!dataSetMap[legend]) {
}; dataSetMap[legend] = { label: legend, data: [], fill: false };
}); }
} else { dataSetMap[legend].data.push({ x: row[1], y: Number(row[2]) });
chartData = data.rows.map((row, i) => { }
return { });
data: row.map(item => Number(item)), chartData = Object.values(dataSetMap);
label: localize('series', 'Series {0}', i)
};
});
}
} else { } else {
if (this.options.columnsAsLabels) { if (this.options.dataDirection === DataDirection.Horizontal) {
chartData = data.rows[0].slice(1).map((row, i) => { if (this.options.labelFirstColumn) {
return { chartData = data.rows.map((row) => {
data: data.rows.map(row => Number(row[i + 1])), return {
label: data.columns[i + 1] data: row.map(item => Number(item)).slice(1),
}; label: row[0]
}); };
});
} else {
chartData = data.rows.map((row, i) => {
return {
data: row.map(item => Number(item)),
label: localize('series', 'Series {0}', i)
};
});
}
} else { } else {
chartData = data.rows[0].slice(1).map((row, i) => { if (this.options.columnsAsLabels) {
return { chartData = data.rows[0].slice(1).map((row, i) => {
data: data.rows.map(row => Number(row[i + 1])), return {
label: localize('series', 'Series {0}', i + 1) data: data.rows.map(row => Number(row[i + 1])),
}; label: data.columns[i + 1]
}); };
});
} else {
chartData = data.rows[0].slice(1).map((row, i) => {
return {
data: data.rows.map(row => Number(row[i + 1])),
label: localize('series', 'Series {0}', i + 1)
};
});
}
} }
} }
@@ -187,6 +221,35 @@ export class Graph implements IInsight {
color: gridLines color: gridLines
} }
}]; }];
if (this.originalType === ChartType.TimeSeries) {
retval = mixin(retval, timeSeriesScales, true, customMixin);
if (options.xAxisMax) {
retval = mixin(retval, {
scales: {
xAxes: [{
type: 'time',
time: {
max: options.xAxisMax
}
}],
}
}, true, customMixin);
}
if (options.xAxisMin) {
retval = mixin(retval, {
scales: {
xAxes: [{
type: 'time',
time: {
min: options.xAxisMin
}
}],
}
}, true, customMixin);
}
}
} }
retval.legend = <ChartJs.ChartLegendOptions>{ retval.legend = <ChartJs.ChartLegendOptions>{
@@ -208,6 +271,12 @@ export class Graph implements IInsight {
public set options(options: IInsightOptions) { public set options(options: IInsightOptions) {
this._options = options; this._options = options;
this.originalType = options.type as ChartType;
if (this.options.type === ChartType.TimeSeries) {
this.options.type = ChartType.Line;
this.options.dataType = DataType.Point;
this.options.dataDirection = DataDirection.Horizontal;
}
this.data = this._data; this.data = this._data;
} }

View File

@@ -7,7 +7,7 @@
import { Graph } from './graphInsight'; import { Graph } from './graphInsight';
import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces'; import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces';
import { DataDirection, ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; import { DataDirection, ChartType, DataType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
import { ImageInsight } from './imageInsight'; import { ImageInsight } from './imageInsight';
import { TableInsight } from './tableInsight'; import { TableInsight } from './tableInsight';
import { IInsightOptions, IInsight, InsightType, IInsightCtor } from './interfaces'; import { IInsightOptions, IInsight, InsightType, IInsightCtor } from './interfaces';
@@ -16,6 +16,7 @@ import { CountInsight } from './countInsight';
import { Builder } from 'vs/base/browser/builder'; import { Builder } from 'vs/base/browser/builder';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Dimension } from 'vs/base/browser/dom'; import { Dimension } from 'vs/base/browser/dom';
import { deepClone } from 'vs/base/common/objects';
const defaultOptions: IInsightOptions = { const defaultOptions: IInsightOptions = {
type: ChartType.Bar, type: ChartType.Bar,
@@ -47,13 +48,13 @@ export class Insight {
} }
public set options(val: IInsightOptions) { public set options(val: IInsightOptions) {
this._options = val; this._options = deepClone(val);
if (this.insight) { if (this.insight) {
// check to see if we need to change the insight type // check to see if we need to change the insight type
if (!this.insight.types.includes(val.type)) { if (!this.insight.types.includes(this.options.type)) {
this.buildInsight(); this.buildInsight();
} else { } else {
this.insight.options = val; this.insight.options = this.options;
} }
} }
} }

View File

@@ -21,6 +21,7 @@ import { CopyKeybind } from 'sql/base/browser/ui/table/plugins/copyKeybind.plugi
import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin'; import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin';
import * as sqlops from 'sqlops'; import * as sqlops from 'sqlops';
import * as pretty from 'pretty-data';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
@@ -477,7 +478,27 @@ class GridTable<T> extends Disposable implements IView {
if (column && (column.isXml || column.isJson)) { if (column && (column.isXml || column.isJson)) {
this.runner.getQueryRows(event.cell.row, 1, this.resultSet.batchId, this.resultSet.id).then(d => { this.runner.getQueryRows(event.cell.row, 1, this.resultSet.batchId, this.resultSet.id).then(d => {
let value = d.resultSubset.rows[0][event.cell.cell - 1]; let value = d.resultSubset.rows[0][event.cell.cell - 1];
let input = this.untitledEditorService.createOrGet(undefined, column.isXml ? 'xml' : 'json', value.displayValue); let content = value.displayValue;
if (column.isXml) {
try {
content = pretty.pd.xml(content);
} catch (e) {
// If Xml fails to parse, fall back on original Xml content
}
} else {
let jsonContent: string = undefined;
try {
jsonContent = JSON.parse(content);
} catch (e) {
// If Json fails to parse, fall back on original Json content
}
if (jsonContent) {
// If Json content was valid and parsed, pretty print content to a string
content = JSON.stringify(jsonContent, undefined, 4);
}
}
let input = this.untitledEditorService.createOrGet(undefined, column.isXml ? 'xml' : 'json', content);
this.editorService.openEditor(input); this.editorService.openEditor(input);
}); });
} }

View File

@@ -18,9 +18,9 @@ import { PanelViewlet } from 'vs/workbench/browser/parts/views/panelViewlet';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import * as DOM from 'vs/base/browser/dom'; import * as DOM from 'vs/base/browser/dom';
import { once, anyEvent } from 'vs/base/common/event'; import { once, anyEvent } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
class ResultsView implements IPanelView { class ResultsView extends Disposable implements IPanelView {
private panelViewlet: PanelViewlet; private panelViewlet: PanelViewlet;
private gridPanel: GridPanel; private gridPanel: GridPanel;
private messagePanel: MessagePanel; private messagePanel: MessagePanel;
@@ -30,10 +30,10 @@ class ResultsView implements IPanelView {
private _state: ResultsViewState; private _state: ResultsViewState;
constructor(private instantiationService: IInstantiationService) { constructor(private instantiationService: IInstantiationService) {
super();
this.panelViewlet = this.instantiationService.createInstance(PanelViewlet, 'resultsView', { showHeaderInTitleWhenSingleView: false }); this.panelViewlet = this._register(this.instantiationService.createInstance(PanelViewlet, 'resultsView', { showHeaderInTitleWhenSingleView: false }));
this.gridPanel = this.instantiationService.createInstance(GridPanel, { title: nls.localize('gridPanel', 'Results'), id: 'gridPanel' }); this.gridPanel = this._register(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.messagePanel = this._register(this.instantiationService.createInstance(MessagePanel, { title: nls.localize('messagePanel', 'Messages'), minimumBodySize: 0, id: 'messagePanel' }));
this.gridPanel.render(); this.gridPanel.render();
this.messagePanel.render(); this.messagePanel.render();
this.panelViewlet.create(this.container).then(() => { this.panelViewlet.create(this.container).then(() => {
@@ -147,9 +147,13 @@ class ResultsTab implements IPanelTab {
public set queryRunner(runner: QueryRunner) { public set queryRunner(runner: QueryRunner) {
this.view.queryRunner = runner; this.view.queryRunner = runner;
} }
public dispose() {
dispose(this.view);
}
} }
export class QueryResultsView { export class QueryResultsView extends Disposable {
private _panelView: TabbedPanel; private _panelView: TabbedPanel;
private _input: QueryResultsInput; private _input: QueryResultsInput;
private resultsTab: ResultsTab; private resultsTab: ResultsTab;
@@ -163,16 +167,17 @@ export class QueryResultsView {
@IInstantiationService instantiationService: IInstantiationService, @IInstantiationService instantiationService: IInstantiationService,
@IQueryModelService private queryModelService: IQueryModelService @IQueryModelService private queryModelService: IQueryModelService
) { ) {
this.resultsTab = new ResultsTab(instantiationService); super();
this.chartTab = new ChartTab(instantiationService); this.resultsTab = this._register(new ResultsTab(instantiationService));
this._panelView = new TabbedPanel(container, { showHeaderWhenSingleView: false }); this.chartTab = this._register(new ChartTab(instantiationService));
this.qpTab = new QueryPlanTab(); this._panelView = this._register(new TabbedPanel(container, { showHeaderWhenSingleView: false }));
this.qpTab = this._register(new QueryPlanTab());
this._panelView.pushTab(this.resultsTab); this._panelView.pushTab(this.resultsTab);
this._panelView.onTabChange(e => { this._register(this._panelView.onTabChange(e => {
if (this.input) { if (this.input) {
this.input.state.activeTab = e; this.input.state.activeTab = e;
} }
}); }));
} }
public style() { public style() {

View File

@@ -10,8 +10,8 @@ import { IPanelView, IPanelTab } from 'sql/base/browser/ui/panel/panel';
import { Dimension } from 'vs/base/browser/dom'; import { Dimension } from 'vs/base/browser/dom';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import * as UUID from 'vs/base/common/uuid';
import { Builder } from 'vs/base/browser/builder'; import { Builder } from 'vs/base/browser/builder';
import { dispose, Disposable } from 'vs/base/common/lifecycle';
export class QueryPlanState { export class QueryPlanState {
xml: string; xml: string;
@@ -25,6 +25,10 @@ export class QueryPlanTab implements IPanelTab {
constructor() { constructor() {
this.view = new QueryPlanView(); this.view = new QueryPlanView();
} }
public dispose() {
dispose(this.view);
}
} }
export class QueryPlanView implements IPanelView { export class QueryPlanView implements IPanelView {
@@ -44,6 +48,12 @@ export class QueryPlanView implements IPanelView {
this.container.style.overflow = 'scroll'; this.container.style.overflow = 'scroll';
} }
dispose() {
this.container.remove();
this.qp = undefined;
this.container = undefined;
}
public layout(dimension: Dimension): void { public layout(dimension: Dimension): void {
this.container.style.width = dimension.width + 'px'; this.container.style.width = dimension.width + 'px';
this.container.style.height = dimension.height + 'px'; this.container.style.height = dimension.height + 'px';