Merge remote-tracking branch 'origin/master' into feature/nativeNotebook

This commit is contained in:
Kevin Cunnane
2018-10-30 16:08:19 -07:00
85 changed files with 5214 additions and 276 deletions

View File

@@ -27,6 +27,7 @@ export class Checkbox extends Widget {
this._el = document.createElement('input');
this._el.type = 'checkbox';
this._el.style.verticalAlign = 'middle';
if (opts.ariaLabel) {
this._el.setAttribute('aria-label', opts.ariaLabel);
@@ -44,6 +45,7 @@ export class Checkbox extends Widget {
});
this._label = document.createElement('span');
this._label.style.verticalAlign = 'middle';
this.label = opts.label;
this.enabled = opts.enabled || true;

View File

@@ -167,17 +167,20 @@
flex-direction: column;
}
.modal.flyout-dialog .dialog-message.error {
.vs .modal.flyout-dialog .dialog-message.error,
.vs-dark .modal.flyout-dialog .dialog-message.error {
background-color:#B62E00 !important;
color:#FFFFFF !important;
}
.modal.flyout-dialog .dialog-message.warning {
.vs .modal.flyout-dialog .dialog-message.warning,
.vs-dark .modal.flyout-dialog .dialog-message.warning {
background-color:#F9E385 !important;
color:#4A4A4A !important;
}
.modal.flyout-dialog .dialog-message.info {
.vs .modal.flyout-dialog .dialog-message.info,
.vs-dark .modal.flyout-dialog .dialog-message.info {
background-color:#0078D7 !important;
color:#FFFFFF !important;
}
@@ -243,6 +246,7 @@
font-size: 11px;
}
.hc-black .dialog-message.warning .close-message-icon,
.close-message-icon {
background: url('close_inverse.svg') center center no-repeat;
}
@@ -251,6 +255,7 @@
background: url('close.svg') center center no-repeat;
}
.hc-black .dialog-message.warning .copy-message-icon,
.copy-message-icon {
background: url('copy_inverse.svg') center center no-repeat;
}
@@ -259,6 +264,7 @@
background: url('copy.svg') center center no-repeat;
}
.hc-black .dialog-message.warning .message-details-icon,
.message-details-icon {
background: url('show_details_inverse.svg') center center no-repeat;
}
@@ -271,6 +277,10 @@
background: url('info_notification_inverse.svg') center center no-repeat;
}
.hc-black .dialog-message.warning .dialog-message-icon {
background: url('warning_notification_inverse.svg') center center no-repeat;
}
.dialog-message.warning .dialog-message-icon {
background: url('warning_notification.svg') center center no-repeat;
}

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/modal';
import { IThemable } from 'vs/platform/theme/common/styler';
import { IThemable, attachButtonStyler } from 'vs/platform/theme/common/styler';
import { Color } from 'vs/base/common/color';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
@@ -23,6 +23,7 @@ import * as TelemetryKeys from 'sql/common/telemetryKeys';
import { localize } from 'vs/nls';
import { MessageLevel } from 'sql/workbench/api/common/sqlExtHostTypes';
import * as os from 'os';
import { IThemeService } from 'vs/platform/theme/common/themeService';
export const MODAL_SHOWING_KEY = 'modalShowing';
export const MODAL_SHOWING_CONTEXT = new RawContextKey<Array<string>>(MODAL_SHOWING_KEY, []);
@@ -153,6 +154,7 @@ export abstract class Modal extends Disposable implements IThemable {
private _partService: IPartService,
private _telemetryService: ITelemetryService,
protected _clipboardService: IClipboardService,
protected _themeService: IThemeService,
_contextKeyService: IContextKeyService,
options?: IModalOptions
) {
@@ -228,6 +230,10 @@ export abstract class Modal extends Disposable implements IThemable {
this.setError(undefined);
});
});
attachButtonStyler(this._toggleMessageDetailButton, this._themeService);
attachButtonStyler(this._copyMessageButton, this._themeService);
attachButtonStyler(this._closeMessageButton, this._themeService);
});
messageContainer.div({ class: 'dialog-message-body' }, (messageBody) => {
messageBody.div({ class: 'dialog-message-summary' }, (summaryContainer) => {
@@ -601,6 +607,7 @@ export abstract class Modal extends Disposable implements IThemable {
this._modalDialog.style('border-style', border ? 'solid' : null);
this._modalDialog.style('border-color', border);
}
if (this._modalHeaderSection) {
this._modalHeaderSection.style('background-color', headerAndFooterBackground);
this._modalHeaderSection.style('border-bottom-width', border ? '1px' : null);
@@ -608,6 +615,13 @@ export abstract class Modal extends Disposable implements IThemable {
this._modalHeaderSection.style('border-bottom-color', border);
}
if (this._modalMessageSection) {
this._modalMessageSection.style('background-color', headerAndFooterBackground);
this._modalMessageSection.style('border-bottom-width', border ? '1px' : null);
this._modalMessageSection.style('border-bottom-style', border ? 'solid' : null);
this._modalMessageSection.style('border-bottom-color', border);
}
if (this._modalBodySection) {
this._modalBodySection.style.backgroundColor = bodyBackground;
}

View File

@@ -93,13 +93,13 @@ export class OptionsDialog extends Modal {
name: string,
options: IModalOptions,
@IPartService partService: IPartService,
@IWorkbenchThemeService private _themeService: IWorkbenchThemeService,
@IWorkbenchThemeService private _workbenchThemeService: IWorkbenchThemeService,
@IContextViewService private _contextViewService: IContextViewService,
@ITelemetryService telemetryService: ITelemetryService,
@IContextKeyService contextKeyService: IContextKeyService,
@IClipboardService clipboardService: IClipboardService
) {
super(title, name, partService, telemetryService, clipboardService, contextKeyService, options);
super(title, name, partService, telemetryService, clipboardService, _workbenchThemeService, contextKeyService, options);
}
public render() {
@@ -115,8 +115,8 @@ export class OptionsDialog extends Modal {
attachButtonStyler(this._okButton, this._themeService);
attachButtonStyler(this._closeButton, this._themeService);
let self = this;
this._register(self._themeService.onDidColorThemeChange(e => self.updateTheme(e)));
self.updateTheme(self._themeService.getColorTheme());
this._register(self._workbenchThemeService.onDidColorThemeChange(e => self.updateTheme(e)));
self.updateTheme(self._workbenchThemeService.getColorTheme());
}
protected renderBody(container: HTMLElement) {

View File

@@ -45,7 +45,7 @@ export class WebViewDialog extends Modal {
protected findInputFocusContextKey: IContextKey<boolean>;
constructor(
@IThemeService private _themeService: IThemeService,
@IThemeService themeService: IThemeService,
@IClipboardService clipboardService: IClipboardService,
@IPartService private _webViewPartService: IPartService,
@ITelemetryService telemetryService: ITelemetryService,
@@ -54,7 +54,7 @@ export class WebViewDialog extends Modal {
@IEnvironmentService private _environmentService: IEnvironmentService,
@IInstantiationService private _instantiationService: IInstantiationService
) {
super('', TelemetryKeys.WebView, _webViewPartService, telemetryService, clipboardService, contextKeyService, { isFlyout: false, hasTitleIcon: true });
super('', TelemetryKeys.WebView, _webViewPartService, telemetryService, clipboardService, themeService, contextKeyService, { isFlyout: false, hasTitleIcon: true });
this._okLabel = localize('webViewDialog.ok', 'OK');
this._closeLabel = localize('webViewDialog.close', 'Close');
}

View File

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

View File

@@ -17,11 +17,13 @@ import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElemen
import { HeightMap, IView as HeightIView, IViewItem as HeightIViewItem } from './heightMap';
import { ArrayIterator } from 'vs/base/common/iterator';
import { mixin } from 'vs/base/common/objects';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
export { Orientation } from 'vs/base/browser/ui/sash/sash';
export interface ISplitViewOptions {
orientation?: Orientation; // default Orientation.VERTICAL
enableResizing?: boolean;
verticalScrollbarVisibility?: ScrollbarVisibility;
}
const defaultOptions: ISplitViewOptions = {
@@ -127,7 +129,7 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
this.options = mixin(options, defaultOptions, false);
this.el = document.createElement('div');
this.scrollable = new ScrollableElement(this.el, {});
this.scrollable = new ScrollableElement(this.el, { vertical: options.verticalScrollbarVisibility });
debounceEvent(this.scrollable.onScroll, (l, e) => e, 25)(e => {
this.render(e.scrollTop, e.height);
this.relayout();

View File

@@ -67,7 +67,7 @@ export class AccountDialog extends Modal {
constructor(
@IPartService partService: IPartService,
@IThemeService private _themeService: IThemeService,
@IThemeService themeService: IThemeService,
@IListService private _listService: IListService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IContextMenuService private _contextMenuService: IContextMenuService,
@@ -82,6 +82,7 @@ export class AccountDialog extends Modal {
partService,
telemetryService,
clipboardService,
themeService,
contextKeyService,
{ hasSpinner: true }
);

View File

@@ -43,7 +43,7 @@ export class AutoOAuthDialog extends Modal {
constructor(
@IPartService partService: IPartService,
@IThemeService private _themeService: IThemeService,
@IThemeService themeService: IThemeService,
@IContextViewService private _contextViewService: IContextViewService,
@ITelemetryService telemetryService: ITelemetryService,
@IContextKeyService contextKeyService: IContextKeyService,
@@ -55,6 +55,7 @@ export class AutoOAuthDialog extends Modal {
partService,
telemetryService,
clipboardService,
themeService,
contextKeyService,
{
isFlyout: true,

View File

@@ -65,7 +65,7 @@ export class FirewallRuleDialog extends Modal {
constructor(
@IAccountPickerService private _accountPickerService: IAccountPickerService,
@IPartService partService: IPartService,
@IWorkbenchThemeService private _themeService: IWorkbenchThemeService,
@IWorkbenchThemeService private _workbenchThemeService: IWorkbenchThemeService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IContextViewService private _contextViewService: IContextViewService,
@ITelemetryService telemetryService: ITelemetryService,
@@ -79,6 +79,7 @@ export class FirewallRuleDialog extends Modal {
partService,
telemetryService,
clipboardService,
_workbenchThemeService,
contextKeyService,
{
isFlyout: true,
@@ -205,8 +206,8 @@ export class FirewallRuleDialog extends Modal {
builder.append(firewallRuleSection);
});
this._register(this._themeService.onDidColorThemeChange(e => this.updateTheme(e)));
this.updateTheme(this._themeService.getColorTheme());
this._register(this._workbenchThemeService.onDidColorThemeChange(e => this.updateTheme(e)));
this.updateTheme(this._workbenchThemeService.getColorTheme());
$(this._IPAddressInput).on(DOM.EventType.CLICK, () => {
this.onFirewallRuleOptionSelected(true);

View File

@@ -23,9 +23,7 @@ export class ConnectionStatusbarItem implements IStatusbarItem {
constructor(
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IEditorGroupsService private _editorGroupService: IEditorGroupsService,
@IEditorService private _editorService: EditorServiceImpl,
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
) {
}

View File

@@ -69,7 +69,7 @@ export function parseTimeString(value: string): number | boolean {
* @param value The number of milliseconds to convert to a timespan string
* @returns A properly formatted timespan string.
*/
export function parseNumAsTimeString(value: number): string {
export function parseNumAsTimeString(value: number, includeFraction: boolean = true): string {
let tempVal = value;
let h = Math.floor(tempVal / msInH);
tempVal %= msInH;
@@ -85,7 +85,7 @@ export function parseNumAsTimeString(value: number): string {
let rs = hs + ':' + ms + ':' + ss;
return tempVal > 0 ? rs + '.' + mss : rs;
return tempVal > 0 && includeFraction ? rs + '.' + mss : rs;
}
export function generateUri(connection: IConnectionProfile, purpose?: 'dashboard' | 'insights' | 'connection'): string {

View File

@@ -87,16 +87,32 @@ export class ConnectionDialogService implements IConnectionDialogService {
@IClipboardService private _clipboardService: IClipboardService,
@ICommandService private _commandService: ICommandService
) { }
private getDefaultProviderName() {
/**
* Gets the default provider with the following actions
* 1. Checks if master provider(map) has data
* 2. If so, filters provider paramter against master map
* 3. Fetches the result array and extracts the first element
* 4. If none of the above data exists, returns 'MSSQL'
* @returns: Default provider as string
*/
private getDefaultProviderName(): string {
let defaultProvider: string;
if (this._providerNameToDisplayNameMap) {
let keys = Object.keys(this._providerNameToDisplayNameMap);
let filteredKeys: string[];
if (keys && keys.length > 0) {
defaultProvider = keys[0];
if (this._params && this._params.providers && this._params.providers.length > 0) {
//Filter providers from master keys.
filteredKeys = keys.filter(key => this._params.providers.includes(key));
}
if (filteredKeys && filteredKeys.length > 0) {
defaultProvider = filteredKeys[0];
}
else {
defaultProvider = keys[0];
}
}
}
if (!defaultProvider && this._workspaceConfigurationService) {
defaultProvider = WorkbenchUtils.getSqlConfigValue<string>(this._workspaceConfigurationService, Constants.defaultEngine);
}

View File

@@ -88,7 +88,7 @@ export class ConnectionDialogWidget extends Modal {
private providerNameToDisplayNameMap: { [providerDisplayName: string]: string },
@IInstantiationService private _instantiationService: IInstantiationService,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IWorkbenchThemeService private _themeService: IWorkbenchThemeService,
@IWorkbenchThemeService private _workbenchThemeService: IWorkbenchThemeService,
@IPartService _partService: IPartService,
@ITelemetryService telemetryService: ITelemetryService,
@IContextKeyService contextKeyService: IContextKeyService,
@@ -96,7 +96,7 @@ export class ConnectionDialogWidget extends Modal {
@IContextViewService private _contextViewService: IContextViewService,
@IClipboardService clipboardService: IClipboardService
) {
super(localize('connection', 'Connection'), TelemetryKeys.Connection, _partService, telemetryService, clipboardService, contextKeyService, { hasSpinner: true, hasErrors: true });
super(localize('connection', 'Connection'), TelemetryKeys.Connection, _partService, telemetryService, clipboardService, _workbenchThemeService, contextKeyService, { hasSpinner: true, hasErrors: true });
}
public refresh(): void {
@@ -194,8 +194,8 @@ export class ConnectionDialogWidget extends Modal {
this.$connectionUIContainer.appendTo(this._bodyBuilder);
let self = this;
this._register(self._themeService.onDidColorThemeChange(e => self.updateTheme(e)));
self.updateTheme(self._themeService.getColorTheme());
this._register(self._workbenchThemeService.onDidColorThemeChange(e => self.updateTheme(e)));
self.updateTheme(self._workbenchThemeService.getColorTheme());
}
/**

View File

@@ -83,7 +83,7 @@ export class ConnectionWidget {
};
public NoneServerGroup: IConnectionProfileGroup = {
id: '',
name: localize('noneServerGroup', '<None>'),
name: localize('noneServerGroup', '<Do not save>'),
parentId: undefined,
color: undefined,
description: undefined,

View File

@@ -120,7 +120,7 @@ export class NewDashboardTabDialog extends Modal {
constructor(
@IPartService partService: IPartService,
@IThemeService private _themeService: IThemeService,
@IThemeService themeService: IThemeService,
@IListService private _listService: IListService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IContextMenuService private _contextMenuService: IContextMenuService,
@@ -135,6 +135,7 @@ export class NewDashboardTabDialog extends Modal {
partService,
telemetryService,
clipboardService,
themeService,
contextKeyService,
{ hasSpinner: true }
);

View File

@@ -10,10 +10,9 @@ 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 { 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 { Color } from 'vs/base/common/color';
import * as types from 'vs/base/common/types';
import { Disposable } from 'vs/base/common/lifecycle';
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;
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({
template: ` <div style="display: block; width: 100%; height: 100%; position: relative">
<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.
*--------------------------------------------------------------------------------------------*/
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 {
Bar = 'bar',
Doughnut = 'doughnut',
@@ -29,4 +34,49 @@ export enum LegendPosition {
export enum DataType {
Number = 'number',
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.
*--------------------------------------------------------------------------------------------*/
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 { 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 * as colors from 'vs/platform/theme/common/colorRegistry';

View File

@@ -5,11 +5,10 @@
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 { 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 {
dataType?: DataType;

View File

@@ -3,10 +3,9 @@
* 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 { 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';

View File

@@ -3,10 +3,9 @@
* 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 { 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 { Color } from 'vs/base/common/color';

View File

@@ -27,7 +27,7 @@ export class BackupDialog extends Modal {
private _moduleRef: any;
constructor(
@IThemeService private _themeService: IThemeService,
@IThemeService themeService: IThemeService,
@IPartService partService: IPartService,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@ITelemetryService telemetryService: ITelemetryService,
@@ -35,7 +35,7 @@ export class BackupDialog extends Modal {
@IInstantiationService private _instantiationService: IInstantiationService,
@IClipboardService clipboardService: IClipboardService
) {
super('', TelemetryKeys.Backup, partService, telemetryService, clipboardService, contextKeyService, { isAngular: true, hasErrors: true });
super('', TelemetryKeys.Backup, partService, telemetryService, clipboardService, themeService, contextKeyService, { isAngular: true, hasErrors: true });
}
protected renderBody(container: HTMLElement) {

View File

@@ -130,14 +130,14 @@ export class RestoreDialog extends Modal {
constructor(
optionsMetadata: sqlops.ServiceOption[],
@IPartService partService: IPartService,
@IThemeService private _themeService: IThemeService,
@IThemeService themeService: IThemeService,
@IContextViewService private _contextViewService: IContextViewService,
@ITelemetryService telemetryService: ITelemetryService,
@IContextKeyService contextKeyService: IContextKeyService,
@IFileBrowserDialogController private fileBrowserDialogService: IFileBrowserDialogController,
@IClipboardService clipboardService: IClipboardService
) {
super(localize('RestoreDialogTitle', 'Restore database'), TelemetryKeys.Restore, partService, telemetryService, clipboardService, contextKeyService, { hasErrors: true, isWide: true, hasSpinner: true });
super(localize('RestoreDialogTitle', 'Restore database'), TelemetryKeys.Restore, partService, telemetryService, clipboardService, themeService, contextKeyService, { hasErrors: true, isWide: true, hasSpinner: true });
this._restoreTitle = localize('restoreDialog.restoreTitle', 'Restore database');
this._databaseTitle = localize('restoreDialog.database', 'Database');
this._backupFileTitle = localize('restoreDialog.backupFile', 'Backup file');

View File

@@ -53,14 +53,14 @@ export class FileBrowserDialog extends Modal {
constructor(title: string,
@IPartService partService: IPartService,
@IWorkbenchThemeService private _themeService: IWorkbenchThemeService,
@IWorkbenchThemeService private _workbenchthemeService: IWorkbenchThemeService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IContextViewService private _contextViewService: IContextViewService,
@ITelemetryService telemetryService: ITelemetryService,
@IContextKeyService contextKeyService: IContextKeyService,
@IClipboardService clipboardService: IClipboardService
) {
super(title, TelemetryKeys.Backup, partService, telemetryService, clipboardService, contextKeyService, { isFlyout: true, hasTitleIcon: false, hasBackButton: true, hasSpinner: true });
super(title, TelemetryKeys.Backup, partService, telemetryService, clipboardService, _workbenchthemeService, contextKeyService, { isFlyout: true, hasTitleIcon: false, hasBackButton: true, hasSpinner: true });
this._viewModel = this._instantiationService.createInstance(FileBrowserViewModel);
this._viewModel.onAddFileTree(args => this.handleOnAddFileTree(args.rootNode, args.selectedNode, args.expandedNodes));
this._viewModel.onPathValidate(args => this.handleOnValidate(args.succeeded, args.message));
@@ -238,7 +238,7 @@ export class FileBrowserDialog extends Modal {
this._register(attachButtonStyler(this._okButton, this._themeService));
this._register(attachButtonStyler(this._cancelButton, this._themeService));
this._register(this._themeService.onDidColorThemeChange(e => this.updateTheme()));
this._register(this._workbenchthemeService.onDidColorThemeChange(e => this.updateTheme()));
}
// Update theming that is specific to file browser

View File

@@ -125,7 +125,7 @@ export class InsightsDialogView extends Modal {
constructor(
private _model: IInsightsDialogModel,
@IInstantiationService private _instantiationService: IInstantiationService,
@IThemeService private _themeService: IThemeService,
@IThemeService themeService: IThemeService,
@IListService private _listService: IListService,
@IPartService partService: IPartService,
@IContextMenuService private _contextMenuService: IContextMenuService,
@@ -135,7 +135,7 @@ export class InsightsDialogView extends Modal {
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService,
@IClipboardService clipboardService: IClipboardService
) {
super(nls.localize("InsightsDialogTitle", "Insights"), TelemetryKeys.Insights, partService, telemetryService, clipboardService, contextKeyService);
super(nls.localize("InsightsDialogTitle", "Insights"), TelemetryKeys.Insights, partService, telemetryService, clipboardService, themeService, contextKeyService);
this._model.onDataChange(e => this.build());
}

View File

@@ -7,7 +7,7 @@
import * as sqlops from 'sqlops';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { JobCacheObject } from './jobManagementService';
import { JobCacheObject, AlertsCacheObject, ProxiesCacheObject, OperatorsCacheObject } from './jobManagementService';
import { Event } from 'vs/base/common/event';
export const SERVICE_ID = 'jobManagementService';
@@ -39,6 +39,10 @@ export interface IJobManagementService {
getCredentials(connectionUri: string): Thenable<sqlops.GetCredentialsResult>;
jobAction(connectionUri: string, jobName: string, action: string): Thenable<sqlops.ResultStatus>;
addToCache(server: string, cache: JobCacheObject);
addToCache(server: string, cache: JobCacheObject | OperatorsCacheObject);
jobCacheObjectMap: { [server: string]: JobCacheObject; };
operatorsCacheObjectMap: { [server: string]: OperatorsCacheObject; };
alertsCacheObjectMap: { [server: string]: AlertsCacheObject; };
proxiesCacheObjectMap: {[server: string]: ProxiesCacheObject };
addToCache(server: string, cache: JobCacheObject | ProxiesCacheObject | AlertsCacheObject | OperatorsCacheObject);
}

View File

@@ -19,7 +19,10 @@ export class JobManagementService implements IJobManagementService {
public readonly onDidChange: Event<void> = this._onDidChange.event;
private _providers: { [handle: string]: sqlops.AgentServicesProvider; } = Object.create(null);
private _jobCacheObject : {[server: string]: JobCacheObject; } = {};
private _jobCacheObjectMap : {[server: string]: JobCacheObject; } = {};
private _operatorsCacheObjectMap: {[server: string]: OperatorsCacheObject; } = {};
private _alertsCacheObject: {[server: string]: AlertsCacheObject; } = {};
private _proxiesCacheObjectMap: {[server: string]: ProxiesCacheObject; } = {};
constructor(
@IConnectionManagementService private _connectionService: IConnectionManagementService
@@ -127,16 +130,36 @@ export class JobManagementService implements IJobManagementService {
}
public get jobCacheObjectMap(): {[server: string]: JobCacheObject;} {
return this._jobCacheObject;
return this._jobCacheObjectMap;
}
public addToCache(server: string, cacheObject: JobCacheObject) {
this._jobCacheObject[server] = cacheObject;
public get alertsCacheObjectMap(): {[server: string]: AlertsCacheObject; } {
return this._alertsCacheObject;
}
public get proxiesCacheObjectMap(): {[server: string]: ProxiesCacheObject; } {
return this._proxiesCacheObjectMap;
}
public get operatorsCacheObjectMap(): {[server: string]: OperatorsCacheObject} {
return this._operatorsCacheObjectMap;
}
public addToCache(server: string, cacheObject: JobCacheObject | OperatorsCacheObject | ProxiesCacheObject | AlertsCacheObject) {
if (cacheObject instanceof JobCacheObject) {
this._jobCacheObjectMap[server] = cacheObject;
} else if (cacheObject instanceof OperatorsCacheObject) {
this._operatorsCacheObjectMap[server] = cacheObject;
} else if (cacheObject instanceof AlertsCacheObject) {
this._alertsCacheObject[server] = cacheObject;
} else if (cacheObject instanceof ProxiesCacheObject) {
this._proxiesCacheObjectMap[server] = cacheObject;
}
}
}
/**
* Server level caching of jobs/job histories
* Server level caching of jobs/job histories and their views
*/
export class JobCacheObject {
_serviceBrand: any;
@@ -231,4 +254,117 @@ export class JobCacheObject {
public setJobSchedules(jobID: string, value: sqlops.AgentJobScheduleInfo[]) {
this._jobSchedules[jobID] = value;
}
}
/**
* Server level caching of Operators
*/
export class OperatorsCacheObject {
_serviceBrand: any;
private _operators: sqlops.AgentOperatorInfo[];
private _dataView: Slick.Data.DataView<any>;
private _serverName: string;
/** Getters */
public get operators(): sqlops.AgentOperatorInfo[] {
return this._operators;
}
public get dataview(): Slick.Data.DataView<any> {
return this._dataView;
}
public get serverName(): string {
return this._serverName;
}
/** Setters */
public set operators(value: sqlops.AgentOperatorInfo[]) {
this._operators = value;
}
public set dataview(value: Slick.Data.DataView<any>) {
this._dataView = value;
}
public set serverName(value: string) {
this._serverName = value;
}
}
/*
* Server level caching of job alerts and the alerts view
*/
export class AlertsCacheObject {
_serviceBrand: any;
private _alerts: sqlops.AgentAlertInfo[];
private _dataView: Slick.Data.DataView<any>;
private _serverName: string;
/** Getters */
public get alerts(): sqlops.AgentAlertInfo[] {
return this._alerts;
}
public get dataview(): Slick.Data.DataView<any> {
return this._dataView;
}
public get serverName(): string {
return this._serverName;
}
/** Setters */
public set alerts(value: sqlops.AgentAlertInfo[]) {
this._alerts = value;
}
public set dataview(value: Slick.Data.DataView<any>) {
this._dataView = value;
}
public set serverName(value: string) {
this._serverName = value;
}
}
/**
* Server level caching of job proxies and proxies view
*/
export class ProxiesCacheObject {
_serviceBrand: any;
private _proxies: sqlops.AgentProxyInfo[];
private _dataView: Slick.Data.DataView<any>;
private _serverName: string;
/**
* Getters
*/
public get proxies(): sqlops.AgentProxyInfo[] {
return this._proxies;
}
public get dataview(): Slick.Data.DataView<any> {
return this._dataView;
}
public get serverName(): string {
return this._serverName;
}
/** Setters */
public set proxies(value: sqlops.AgentProxyInfo[]) {
this._proxies = value;
}
public set dataview(value: Slick.Data.DataView<any>) {
this._dataView = value;
}
public set serverName(value: string) {
this._serverName = value;
}
}

View File

@@ -104,6 +104,16 @@ jobhistory-component {
display: inline-block;
}
#operatorsDiv .joboperatorsview-grid .slick-cell.l1.r1 .operatorview-operatornametext,
#alertsDiv .jobalertsview-grid .slick-cell.l1.r1 .alertview-alertnametext,
#proxiesDiv .jobproxiesview-grid .slick-cell.l1.r1 .proxyview-proxynametext {
text-overflow: ellipsis;
width: 100%;
overflow: hidden;
white-space: nowrap;
display: inline-block;
}
#jobsDiv .job-with-error {
border-bottom: none;
}
@@ -390,4 +400,18 @@ jobsview-component .jobview-grid .slick-cell.error-row {
.overview-container > .overview-tab > label {
margin-bottom: 0px;
}
#operatorsDiv .operatorview-operatornameindicatorenabled,
#alertsDiv .alertview-alertnameindicatorenabled,
#proxiesDiv .proxyview-proxynameindicatorenabled {
width: 5px;
background: green;
}
#operatorsDiv .operatorview-operatornameindicatordisabled,
#alertsDiv .alertview-alertnameindicatordisabled,
#proxiesDiv .proxyview-proxynameindicatordisabled {
width: 5px;
background: red;
}

View File

@@ -30,9 +30,11 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IDashboardService } from 'sql/services/dashboard/common/dashboardService';
import { AlertsCacheObject } from 'sql/parts/jobManagement/common/jobManagementService';
import { RowDetailView } from 'sql/base/browser/ui/table/plugins/rowdetailview';
export const VIEW_SELECTOR: string = 'jobalertsview-component';
export const ROW_HEIGHT: number = 30;
export const ROW_HEIGHT: number = 45;
@Component({
selector: VIEW_SELECTOR,
@@ -42,11 +44,17 @@ export const ROW_HEIGHT: number = 30;
export class AlertsViewComponent extends JobManagementView implements OnInit {
private columns: Array<Slick.Column<any>> = [
{ name: nls.localize('jobAlertColumns.name', 'Name'), field: 'name', width: 200, id: 'name' },
{ name: nls.localize('jobAlertColumns.lastOccurrenceDate', 'Last Occurrence'), field: 'lastOccurrenceDate', width: 200, id: 'lastOccurrenceDate' },
{ name: nls.localize('jobAlertColumns.enabled', 'Enabled'), field: 'enabled', width: 200, id: 'enabled' },
{ name: nls.localize('jobAlertColumns.databaseName', 'Database Name'), field: 'databaseName', width: 200, id: 'databaseName' },
{ name: nls.localize('jobAlertColumns.categoryName', 'Category Name'), field: 'categoryName', width: 200, id: 'categoryName' },
{
name: nls.localize('jobAlertColumns.name', 'Name'),
field: 'name',
formatter: (row, cell, value, columnDef, dataContext) => this.renderName(row, cell, value, columnDef, dataContext),
width: 500,
id: 'name'
},
{ name: nls.localize('jobAlertColumns.lastOccurrenceDate', 'Last Occurrence'), field: 'lastOccurrenceDate', width: 150, id: 'lastOccurrenceDate' },
{ name: nls.localize('jobAlertColumns.enabled', 'Enabled'), field: 'enabled', width: 80, id: 'enabled' },
{ name: nls.localize('jobAlertColumns.delayBetweenResponses', 'Delay Between Responses (in secs)'), field: 'delayBetweenResponses', width: 200, id: 'delayBetweenResponses' },
{ name: nls.localize('jobAlertColumns.categoryName', 'Category Name'), field: 'categoryName', width: 250, id: 'categoryName' },
];
private options: Slick.GridOptions<any> = {
@@ -59,6 +67,7 @@ export class AlertsViewComponent extends JobManagementView implements OnInit {
private dataView: any;
private _isCloud: boolean;
private _alertsCacheObject: AlertsCacheObject;
@ViewChild('jobalertsgrid') _gridEl: ElementRef;
@@ -78,6 +87,15 @@ export class AlertsViewComponent extends JobManagementView implements OnInit {
@Inject(IDashboardService) _dashboardService: IDashboardService) {
super(commonService, _dashboardService, contextMenuService, keybindingService, instantiationService);
this._isCloud = commonService.connectionManagementService.connectionInfo.serverInfo.isCloud;
let alertsCacheObjectMap = this._jobManagementService.alertsCacheObjectMap;
let alertsCache = alertsCacheObjectMap[this._serverName];
if (alertsCache) {
this._alertsCacheObject = alertsCache;
} else {
this._alertsCacheObject = new AlertsCacheObject();
this._alertsCacheObject.serverName = this._serverName;
this._jobManagementService.addToCache(this._serverName, this._alertsCacheObject);
}
}
ngOnInit(){
@@ -92,44 +110,67 @@ export class AlertsViewComponent extends JobManagementView implements OnInit {
height = 0;
}
this._table.layout(new dom.Dimension(
dom.getContentWidth(this._gridEl.nativeElement),
height));
if (this._table) {
this._table.layout(new dom.Dimension(
dom.getContentWidth(this._gridEl.nativeElement),
height));
}
}
onFirstVisible() {
let self = this;
let cached: boolean = false;
if (this._alertsCacheObject.serverName === this._serverName) {
if (this._alertsCacheObject.alerts && this._alertsCacheObject.alerts.length > 0) {
cached = true;
this.alerts = this._alertsCacheObject.alerts;
}
}
let columns = this.columns.map((column) => {
column.rerenderOnResize = true;
return column;
});
this.dataView = new Slick.Data.DataView();
this.dataView = new Slick.Data.DataView({ inlineFilters: false });
let rowDetail = new RowDetailView({
cssClass: '_detail_selector',
useRowClick: false,
panelRows: 1
});
columns.unshift(rowDetail.getColumnDefinition());
$(this._gridEl.nativeElement).empty();
$(this.actionBarContainer.nativeElement).empty();
this.initActionBar();
this._table = new Table(this._gridEl.nativeElement, {columns}, this.options);
this._table.grid.setData(this.dataView, true);
this._register(this._table.onContextMenu(e => {
self.openContextMenu(e);
}));
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
this._jobManagementService.getAlerts(ownerUri).then((result) => {
if (result && result.alerts) {
self.alerts = result.alerts;
self.onAlertsAvailable(result.alerts);
} else {
// TODO: handle error
}
// check for cached state
if (cached && this._agentViewComponent.refresh !== true) {
self.onAlertsAvailable(this.alerts);
this._showProgressWheel = false;
if (this.isVisible) {
this._cd.detectChanges();
}
});
} else {
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
this._jobManagementService.getAlerts(ownerUri).then((result) => {
if (result && result.alerts) {
self.alerts = result.alerts;
self._alertsCacheObject.alerts = result.alerts;
self.onAlertsAvailable(result.alerts);
} else {
// TODO: handle error
}
this._showProgressWheel = false;
if (this.isVisible) {
this._cd.detectChanges();
}
});
}
}
private onAlertsAvailable(alerts: sqlops.AgentAlertInfo[]) {
@@ -139,7 +180,7 @@ export class AlertsViewComponent extends JobManagementView implements OnInit {
name: item.name,
lastOccurrenceDate: item.lastOccurrenceDate,
enabled: item.isEnabled,
databaseName: item.databaseName,
delayBetweenResponses: item.delayBetweenResponses,
categoryName: item.categoryName
};
});
@@ -147,6 +188,7 @@ export class AlertsViewComponent extends JobManagementView implements OnInit {
this.dataView.beginUpdate();
this.dataView.setItems(items);
this.dataView.endUpdate();
this._alertsCacheObject.dataview = this.dataView;
this._table.autosizeColumns();
this._table.resizeCanvas();
}
@@ -164,6 +206,17 @@ export class AlertsViewComponent extends JobManagementView implements OnInit {
: undefined;
}
private renderName(row, cell, value, columnDef, dataContext) {
let resultIndicatorClass = dataContext.enabled ? 'alertview-alertnameindicatorenabled' :
'alertview-alertnameindicatordisabled';
return '<table class="alertview-alertnametable"><tr class="alertview-alertnamerow">' +
'<td nowrap class=' + resultIndicatorClass + '></td>' +
'<td nowrap class="alertview-alertnametext">' + dataContext.name + '</td>' +
'</tr></table>';
}
public openCreateAlertDialog() {
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
this._jobManagementService.getJobs(ownerUri).then((result) => {

View File

@@ -63,7 +63,6 @@ export class JobHistoryComponent extends JobManagementView implements OnInit {
private _jobCacheObject: JobCacheObject;
private _agentJobInfo: sqlops.AgentJobInfo;
private _noJobsAvailable: boolean = false;
private _serverName: string;
private static readonly INITIAL_TREE_HEIGHT: number = 780;
private static readonly HEADING_HEIGHT: number = 24;

View File

@@ -28,6 +28,7 @@ export abstract class JobManagementView extends TabChild implements AfterContent
protected _parentComponent: AgentViewComponent;
protected _table: Table<any>;
protected _actionBar: Taskbar;
protected _serverName: string;
public contextAction: any;
@ViewChild('actionbarContainer') protected actionBarContainer: ElementRef;
@@ -41,6 +42,7 @@ export abstract class JobManagementView extends TabChild implements AfterContent
super();
let self = this;
this._serverName = this._commonService.connectionManagementService.connectionInfo.connectionProfile.serverName;
this._dashboardService.onLayout((d) => {
self.layout();
});

View File

@@ -78,7 +78,6 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
private rowDetail: RowDetailView;
private filterPlugin: any;
private dataView: any;
private _serverName: string;
private _isCloud: boolean;
private filterStylingMap: { [columnName: string]: [any]; } = {};
private filterStack = ['start'];
@@ -109,7 +108,6 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
) {
super(commonService, _dashboardService, contextMenuService, keybindingService, instantiationService);
let jobCacheObjectMap = this._jobManagementService.jobCacheObjectMap;
this._serverName = commonService.connectionManagementService.connectionInfo.connectionProfile.serverName;
let jobCache = jobCacheObjectMap[this._serverName];
if (jobCache) {
this._jobCacheObject = jobCache;

View File

@@ -30,9 +30,11 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { IAction } from 'vs/base/common/actions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IDashboardService } from 'sql/services/dashboard/common/dashboardService';
import { OperatorsCacheObject } from 'sql/parts/jobManagement/common/jobManagementService';
import { RowDetailView } from 'sql/base/browser/ui/table/plugins/rowdetailview';
export const VIEW_SELECTOR: string = 'joboperatorsview-component';
export const ROW_HEIGHT: number = 30;
export const ROW_HEIGHT: number = 45;
@Component({
selector: VIEW_SELECTOR,
@@ -43,7 +45,13 @@ export const ROW_HEIGHT: number = 30;
export class OperatorsViewComponent extends JobManagementView implements OnInit {
private columns: Array<Slick.Column<any>> = [
{ name: nls.localize('jobOperatorsView.name', 'Name'), field: 'name', width: 200, id: 'name' },
{
name: nls.localize('jobOperatorsView.name', 'Name'),
field: 'name',
formatter: (row, cell, value, columnDef, dataContext) => this.renderName(row, cell, value, columnDef, dataContext),
width: 200,
id: 'name'
},
{ name: nls.localize('jobOperatorsView.emailAddress', 'Email Address'), field: 'emailAddress', width: 200, id: 'emailAddress' },
{ name: nls.localize('jobOperatorsView.enabled', 'Enabled'), field: 'enabled', width: 200, id: 'enabled' },
];
@@ -57,8 +65,8 @@ export class OperatorsViewComponent extends JobManagementView implements OnInit
};
private dataView: any;
private _serverName: string;
private _isCloud: boolean;
private _operatorsCacheObject: OperatorsCacheObject;
@ViewChild('operatorsgrid') _gridEl: ElementRef;
@@ -79,6 +87,15 @@ export class OperatorsViewComponent extends JobManagementView implements OnInit
) {
super(commonService, _dashboardService, contextMenuService, keybindingService, instantiationService);
this._isCloud = commonService.connectionManagementService.connectionInfo.serverInfo.isCloud;
let operatorsCacheObject = this._jobManagementService.operatorsCacheObjectMap;
let operatorsCache = operatorsCacheObject[this._serverName];
if (operatorsCache) {
this._operatorsCacheObject = operatorsCache;
} else {
this._operatorsCacheObject = new OperatorsCacheObject();
this._operatorsCacheObject.serverName = this._serverName;
this._jobManagementService.addToCache(this._serverName, this._operatorsCacheObject);
}
}
ngOnInit(){
@@ -93,19 +110,36 @@ export class OperatorsViewComponent extends JobManagementView implements OnInit
height = 0;
}
this._table.layout(new dom.Dimension(
dom.getContentWidth(this._gridEl.nativeElement),
height));
if (this._table) {
this._table.layout(new dom.Dimension(
dom.getContentWidth(this._gridEl.nativeElement),
height));
}
}
onFirstVisible() {
let self = this;
let cached: boolean = false;
if (this._operatorsCacheObject.serverName === this._serverName) {
if (this._operatorsCacheObject.operators && this._operatorsCacheObject.operators.length > 0) {
cached = true;
this.operators = this._operatorsCacheObject.operators;
}
}
let columns = this.columns.map((column) => {
column.rerenderOnResize = true;
return column;
});
this.dataView = new Slick.Data.DataView();
this.dataView = new Slick.Data.DataView({ inlineFilters: false });
let rowDetail = new RowDetailView({
cssClass: '_detail_selector',
useRowClick: false,
panelRows: 1
});
columns.unshift(rowDetail.getColumnDefinition());
$(this._gridEl.nativeElement).empty();
$(this.actionBarContainer.nativeElement).empty();
@@ -117,20 +151,29 @@ export class OperatorsViewComponent extends JobManagementView implements OnInit
self.openContextMenu(e);
}));
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
this._jobManagementService.getOperators(ownerUri).then((result) => {
if (result && result.operators) {
self.operators = result.operators;
self.onOperatorsAvailable(result.operators);
} else {
// TODO: handle error
}
// check for cached state
if (cached && this._agentViewComponent.refresh !== true) {
this.onOperatorsAvailable(this.operators);
this._showProgressWheel = false;
if (this.isVisible) {
this._cd.detectChanges();
}
});
} else {
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
this._jobManagementService.getOperators(ownerUri).then((result) => {
if (result && result.operators) {
self.operators = result.operators;
self._operatorsCacheObject.operators = result.operators;
self.onOperatorsAvailable(result.operators);
} else {
// TODO: handle error
}
this._showProgressWheel = false;
if (this.isVisible) {
this._cd.detectChanges();
}
});
}
}
private onOperatorsAvailable(operators: sqlops.AgentOperatorInfo[]) {
@@ -146,6 +189,7 @@ export class OperatorsViewComponent extends JobManagementView implements OnInit
this.dataView.beginUpdate();
this.dataView.setItems(items);
this.dataView.endUpdate();
this._operatorsCacheObject.dataview = this.dataView;
this._table.autosizeColumns();
this._table.resizeCanvas();
}
@@ -163,6 +207,15 @@ export class OperatorsViewComponent extends JobManagementView implements OnInit
: undefined;
}
private renderName(row, cell, value, columnDef, dataContext) {
let resultIndicatorClass = dataContext.enabled ? 'operatorview-operatornameindicatorenabled' :
'operatorview-operatornameindicatordisabled';
return '<table class="operatorview-operatornametable"><tr class="operatorview-operatornamerow">' +
'<td nowrap class=' + resultIndicatorClass + '></td>' +
'<td nowrap class="operatorview-operatornametext">' + dataContext.name + '</td>' +
'</tr></table>';
}
public openCreateOperatorDialog() {
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
this._commandService.executeCommand('agent.openOperatorDialog', ownerUri);

View File

@@ -30,9 +30,11 @@ import { IAction } from 'vs/base/common/actions';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IDashboardService } from 'sql/services/dashboard/common/dashboardService';
import { ProxiesCacheObject } from 'sql/parts/jobManagement/common/jobManagementService';
import { RowDetailView } from 'sql/base/browser/ui/table/plugins/rowdetailview';
export const VIEW_SELECTOR: string = 'jobproxiesview-component';
export const ROW_HEIGHT: number = 30;
export const ROW_HEIGHT: number = 45;
@Component({
selector: VIEW_SELECTOR,
@@ -46,8 +48,16 @@ export class ProxiesViewComponent extends JobManagementView implements OnInit {
private RefreshText: string = nls.localize('jobProxyToolbar-Refresh', "Refresh");
private columns: Array<Slick.Column<any>> = [
{ name: nls.localize('jobProxiesView.accountName', 'Account Name'), field: 'accountName', width: 200, id: 'accountName' },
{
name: nls.localize('jobProxiesView.accountName', 'Account Name'),
field: 'accountName',
formatter: (row, cell, value, columnDef, dataContext) => this.renderName(row, cell, value, columnDef, dataContext),
width: 200,
id: 'accountName'
},
{ name: nls.localize('jobProxiesView.credentialName', 'Credential Name'), field: 'credentialName', width: 200, id: 'credentialName' },
{ name: nls.localize('jobProxiesView.description', 'Description'), field: 'description', width: 200, id: 'description'},
{ name: nls.localize('jobProxiesView.isEnabled', 'Enabled'), field: 'isEnabled', width: 200, id: 'isEnabled'}
];
private options: Slick.GridOptions<any> = {
@@ -59,8 +69,8 @@ export class ProxiesViewComponent extends JobManagementView implements OnInit {
};
private dataView: any;
private _serverName: string;
private _isCloud: boolean;
private _proxiesCacheObject: ProxiesCacheObject;
public proxies: sqlops.AgentProxyInfo[];
public readonly contextAction = NewProxyAction;
@@ -81,6 +91,15 @@ export class ProxiesViewComponent extends JobManagementView implements OnInit {
) {
super(commonService, _dashboardService, contextMenuService, keybindingService, instantiationService);
this._isCloud = commonService.connectionManagementService.connectionInfo.serverInfo.isCloud;
let proxiesCacheObjectMap = this._jobManagementService.proxiesCacheObjectMap;
let proxiesCacheObject = proxiesCacheObjectMap[this._serverName];
if (proxiesCacheObject) {
this._proxiesCacheObject = proxiesCacheObject;
} else {
this._proxiesCacheObject = new ProxiesCacheObject();
this._proxiesCacheObject.serverName = this._serverName;
this._jobManagementService.addToCache(this._serverName, this._proxiesCacheObject);
}
}
ngOnInit(){
@@ -95,19 +114,35 @@ export class ProxiesViewComponent extends JobManagementView implements OnInit {
height = 0;
}
this._table.layout(new dom.Dimension(
dom.getContentWidth(this._gridEl.nativeElement),
height));
if (this._table) {
this._table.layout(new dom.Dimension(
dom.getContentWidth(this._gridEl.nativeElement),
height));
}
}
onFirstVisible() {
let self = this;
let cached: boolean = false;
if (this._proxiesCacheObject.serverName === this._serverName) {
if (this._proxiesCacheObject.proxies && this._proxiesCacheObject.proxies.length > 0) {
cached = true;
this.proxies = this._proxiesCacheObject.proxies;
}
}
let columns = this.columns.map((column) => {
column.rerenderOnResize = true;
return column;
});
this.dataView = new Slick.Data.DataView();
this.dataView = new Slick.Data.DataView({ inlineFilters: false });
let rowDetail = new RowDetailView({
cssClass: '_detail_selector',
useRowClick: false,
panelRows: 1
});
columns.unshift(rowDetail.getColumnDefinition());
$(this._gridEl.nativeElement).empty();
$(this.actionBarContainer.nativeElement).empty();
@@ -119,20 +154,29 @@ export class ProxiesViewComponent extends JobManagementView implements OnInit {
self.openContextMenu(e);
}));
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
this._jobManagementService.getProxies(ownerUri).then((result) => {
if (result && result.proxies) {
self.proxies = result.proxies;
self.onProxiesAvailable(result.proxies);
} else {
// TODO: handle error
}
// checked for cached state
if (cached && this._agentViewComponent.refresh !== true) {
self.onProxiesAvailable(this.proxies);
this._showProgressWheel = false;
if (this.isVisible) {
this._cd.detectChanges();
}
});
} else {
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
this._jobManagementService.getProxies(ownerUri).then((result) => {
if (result && result.proxies) {
self.proxies = result.proxies;
self._proxiesCacheObject.proxies = result.proxies;
self.onProxiesAvailable(result.proxies);
} else {
// TODO: handle error
}
this._showProgressWheel = false;
if (this.isVisible) {
this._cd.detectChanges();
}
});
}
}
private onProxiesAvailable(proxies: sqlops.AgentProxyInfo[]) {
@@ -140,13 +184,16 @@ export class ProxiesViewComponent extends JobManagementView implements OnInit {
return {
id: item.accountName,
accountName: item.accountName,
credentialName: item.credentialName
credentialName: item.credentialName,
description: item.description,
isEnabled: item.isEnabled
};
});
this.dataView.beginUpdate();
this.dataView.setItems(items);
this.dataView.endUpdate();
this._proxiesCacheObject.dataview = this.dataView;
this._table.autosizeColumns();
this._table.resizeCanvas();
}
@@ -164,6 +211,15 @@ export class ProxiesViewComponent extends JobManagementView implements OnInit {
: undefined;
}
private renderName(row, cell, value, columnDef, dataContext) {
let resultIndicatorClass = dataContext.isEnabled ? 'proxyview-proxynameindicatorenabled' :
'proxyview-proxynameindicatordisabled';
return '<table class="proxyview-proxynametable"><tr class="proxyview-proxynamerow">' +
'<td nowrap class=' + resultIndicatorClass + '></td>' +
'<td nowrap class="proxyview-proxynametext">' + dataContext.accountName + '</td>' +
'</tr></table>';
}
public openCreateProxyDialog() {
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
this._jobManagementService.getCredentials(ownerUri).then((result) => {

View File

@@ -39,7 +39,7 @@
.modelview-toolbar-container .modelview-toolbar-title {
padding: 4px;
font-size: 14px;
font-size: 13px;
cursor: pointer;
}
@@ -57,8 +57,8 @@
/* Vertical button handling */
.modelview-toolbar-container.toolbar-vertical .modelview-toolbar-component modelview-button .monaco-text-button.icon {
padding: 22px 16px 22px 16px;
background-size: 22px;
padding: 20px 16px 20px 16px;
background-size: 20px;
margin-right: 0.3em;
background-position: 50% 50%;

View File

@@ -51,13 +51,13 @@ export class ServerGroupDialog extends Modal {
constructor(
@IPartService partService: IPartService,
@IThemeService private _themeService: IThemeService,
@IThemeService themeService: IThemeService,
@IContextViewService private _contextViewService: IContextViewService,
@ITelemetryService telemetryService: ITelemetryService,
@IContextKeyService contextKeyService: IContextKeyService,
@IClipboardService clipboardService: IClipboardService
) {
super(localize('ServerGroupsDialogTitle', 'Server Groups'), TelemetryKeys.ServerGroups, partService, telemetryService, clipboardService, contextKeyService);
super(localize('ServerGroupsDialogTitle', 'Server Groups'), TelemetryKeys.ServerGroups, partService, telemetryService, clipboardService, themeService, contextKeyService);
}
public render() {

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!sql/parts/profiler/media/profiler';
import { IProfilerService } from 'sql/parts/profiler/service/interfaces';
import { IProfilerController } from 'sql/parts/profiler/editor/controller/interfaces';
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
@@ -157,7 +157,7 @@ export class ProfilerClear extends Action {
public static LABEL = nls.localize('profiler.clear', "Clear Data");
constructor(id: string, label: string) {
super(id, label);
super(id, label, 'clear-results');
}
run(input: ProfilerInput): TPromise<void> {

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./media/profilerDialog';
import 'vs/css!sql/parts/profiler/media/profiler';
import { Modal } from 'sql/base/browser/ui/modal/modal';
import { attachModalDialogStyler } from 'sql/common/theme/styler';
@@ -314,13 +314,13 @@ export class ProfilerColumnEditorDialog extends Modal {
constructor(
@IPartService _partService: IPartService,
@IThemeService private _themeService: IThemeService,
@IThemeService themeService: IThemeService,
@ITelemetryService telemetryService: ITelemetryService,
@IContextKeyService contextKeyService: IContextKeyService,
@IContextViewService private _contextViewService: IContextViewService,
@IClipboardService clipboardService: IClipboardService
) {
super(nls.localize('profilerColumnDialog.profiler', 'Profiler'), TelemetryKeys.Profiler, _partService, telemetryService, clipboardService, contextKeyService);
super(nls.localize('profilerColumnDialog.profiler', 'Profiler'), TelemetryKeys.Profiler, _partService, telemetryService, clipboardService, themeService, contextKeyService);
}
public render(): void {

View File

@@ -113,6 +113,7 @@ export class ProfilerTableEditor extends BaseEditor implements IProfilerControll
this._stateListener.dispose();
}
this._stateListener = input.state.addChangeListener(e => this._onStateChange(e));
input.data.onRowCountChange(() => { this._profilerTable.updateRowCount(); });
if (this._findCountChangeListener) {
this._findCountChangeListener.dispose();

View File

@@ -252,6 +252,7 @@ export class ProfilerEditor extends BaseEditor {
{ action: this._pauseAction },
{ action: this._autoscrollAction },
{ action: this._instantiationService.createInstance(Actions.ProfilerClear, Actions.ProfilerClear.ID, Actions.ProfilerClear.LABEL) },
{ element: Taskbar.createTaskbarSeparator() },
{ element: viewTemplateContainer },
{ element: Taskbar.createTaskbarSeparator() },
{ element: this._connectionInfoText }
@@ -344,6 +345,10 @@ export class ProfilerEditor extends BaseEditor {
]
}, { forceFitColumns: true });
this._detailTableData.onRowCountChange(() => {
this._detailTable.updateRowCount();
});
this._tabbedPanel.pushTab({
identifier: 'detailTable',
title: nls.localize('details', "Details"),

View File

@@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.065 13H15v2H2.056v-2h5.009zm3.661-12H7.385L8.44 2.061 7.505 3H15V1h-4.274zM3.237 9H2.056v2H15V9H3.237zm4.208-4l.995 1-.995 1H15V5H7.445z" fill="#424242"/><path d="M5.072 4.03L7.032 6 5.978 7.061l-1.96-1.97-1.961 1.97L1 6l1.96-1.97L1 2.061 2.056 1l1.96 1.97L5.977 1l1.057 1.061L5.072 4.03z" fill="#A1260D"/></svg>

After

Width:  |  Height:  |  Size: 419 B

View File

@@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.065 13H15v2H2.056v-2h5.009zm3.661-12H7.385L8.44 2.061 7.505 3H15V1h-4.274zM3.237 9H2.056v2H15V9H3.237zm4.208-4l.995 1-.995 1H15V5H7.445z" fill="#C5C5C5"/><path d="M5.072 4.03L7.032 6 5.978 7.061l-1.96-1.97-1.961 1.97L1 6l1.96-1.97L1 2.061 2.056 1l1.96 1.97L5.977 1l1.057 1.061L5.072 4.03z" fill="#F48771"/></svg>

After

Width:  |  Height:  |  Size: 419 B

View File

@@ -11,3 +11,11 @@
.tree-row > * {
display: inline-block;
}
.vs .icon.clear-results {
background-image: url('clear.svg');
}
.vs-dark .icon.clear-results {
background-image: url('clear_inverse.svg');
}

View File

@@ -120,7 +120,8 @@ actionRegistry.registerWorkbenchAction(
new SyncActionDescriptor(
RunCurrentQueryWithActualPlanKeyboardAction,
RunCurrentQueryWithActualPlanKeyboardAction.ID,
RunCurrentQueryWithActualPlanKeyboardAction.LABEL
RunCurrentQueryWithActualPlanKeyboardAction.LABEL,
{ primary: KeyMod.CtrlCmd | KeyCode.KEY_M }
),
RunCurrentQueryWithActualPlanKeyboardAction.LABEL
);

View File

@@ -9,7 +9,6 @@ import QueryRunner from 'sql/parts/query/execution/queryRunner';
import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { IDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle';
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorCloseEvent } from 'vs/workbench/common/editor';
import { append, $, hide, show } from 'vs/base/browser/dom';
@@ -25,7 +24,6 @@ export class RowCountStatusBarItem implements IStatusbarItem {
constructor(
@IEditorService private _editorService: EditorServiceImpl,
@IEditorGroupsService private _editorGroupService: IEditorGroupsService,
@IQueryModelService private _queryModelService: IQueryModelService
) { }
@@ -36,7 +34,7 @@ export class RowCountStatusBarItem implements IStatusbarItem {
];
this._element = append(container, $('.query-statusbar-group'));
this._flavorElement = append(this._element, $('a.editor-status-selection'));
this._flavorElement = append(this._element, $('.editor-status-selection'));
this._flavorElement.title = nls.localize('rowStatus', "Row Count");
hide(this._flavorElement);
@@ -65,11 +63,10 @@ export class RowCountStatusBarItem implements IStatusbarItem {
if (queryRunner) {
if (queryRunner.hasCompleted) {
this._displayValue(queryRunner);
} else if (queryRunner.isExecuting) {
this.dispose = queryRunner.addListener('complete', () => {
this._displayValue(queryRunner);
});
}
this.dispose = queryRunner.onQueryEnd(e => {
this._displayValue(queryRunner);
});
} else {
this.dispose = this._queryModelService.onRunQueryComplete(e => {
if (e === currentUri) {

View File

@@ -0,0 +1,107 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
import { IQueryModelService } from '../execution/queryModel';
import QueryRunner from 'sql/parts/query/execution/queryRunner';
import { parseNumAsTimeString } from 'sql/parts/connection/common/utils';
import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { IDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorCloseEvent } from 'vs/workbench/common/editor';
import { append, $, hide, show } from 'vs/base/browser/dom';
import * as nls from 'vs/nls';
import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor';
import { IntervalTimer } from 'vs/base/common/async';
export class TimeElapsedStatusBarItem implements IStatusbarItem {
private _element: HTMLElement;
private _flavorElement: HTMLElement;
private dispose: IDisposable[] = [];
private intervalTimer = new IntervalTimer();
constructor(
@IEditorService private _editorService: EditorServiceImpl,
@IQueryModelService private _queryModelService: IQueryModelService
) { }
render(container: HTMLElement): IDisposable {
let disposables = [
this._editorService.onDidVisibleEditorsChange(() => this._onEditorsChanged()),
this._editorService.onDidCloseEditor(event => this._onEditorClosed(event))
];
this._element = append(container, $('.query-statusbar-group'));
this._flavorElement = append(this._element, $('.editor-status-selection'));
this._flavorElement.title = nls.localize('timeElapsed', "Time Elapsed");
hide(this._flavorElement);
this._showStatus();
return combinedDisposable(disposables);
}
private _onEditorsChanged() {
this._showStatus();
}
private _onEditorClosed(event: IEditorCloseEvent) {
hide(this._flavorElement);
}
// Show/hide query status for active editor
private _showStatus(): void {
this.intervalTimer.cancel();
hide(this._flavorElement);
dispose(this.dispose);
this._flavorElement.innerText = '';
this.dispose = [];
let activeEditor = this._editorService.activeControl;
if (activeEditor) {
let currentUri = WorkbenchUtils.getEditorUri(activeEditor.input);
if (currentUri) {
let queryRunner = this._queryModelService.getQueryRunner(currentUri);
if (queryRunner) {
if (queryRunner.hasCompleted || queryRunner.isExecuting) {
this._displayValue(queryRunner);
}
this.dispose.push(queryRunner.onQueryStart(e => {
this._displayValue(queryRunner);
}));
this.dispose.push(queryRunner.onQueryEnd(e => {
this._displayValue(queryRunner);
}));
} else {
this.dispose.push(this._queryModelService.onRunQueryStart(e => {
if (e === currentUri) {
this._displayValue(this._queryModelService.getQueryRunner(currentUri));
}
}));
this.dispose.push(this._queryModelService.onRunQueryComplete(e => {
if (e === currentUri) {
this._displayValue(this._queryModelService.getQueryRunner(currentUri));
}
}));
}
}
}
}
private _displayValue(runner: QueryRunner) {
this.intervalTimer.cancel();
if (runner.isExecuting) {
this.intervalTimer.cancelAndSet(() => {
this._flavorElement.innerText = parseNumAsTimeString(Date.now() - runner.queryStartTime.getTime(), false);
}, 1000);
this._flavorElement.innerText = parseNumAsTimeString(Date.now() - runner.queryStartTime.getTime(), false);
} else {
this._flavorElement.innerText = parseNumAsTimeString(runner.queryEndTime.getTime() - runner.queryStartTime.getTime(), false);
}
show(this._flavorElement);
}
}

View File

@@ -18,7 +18,8 @@ export enum ControlType {
combo,
numberInput,
input,
checkbox
checkbox,
dateInput
}
export interface IChartOption {
@@ -115,6 +116,20 @@ const xAxisMaxInput: IChartOption = {
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 = {
label: localize('dataTypeLabel', 'Data Type'),
type: ControlType.combo,
@@ -150,7 +165,11 @@ export const ChartOptions: IChartOptions = {
[ChartType.TimeSeries]: [
legendInput,
yAxisLabelInput,
xAxisLabelInput
yAxisMinInput,
yAxisMaxInput,
xAxisLabelInput,
xAxisMinDateInput,
xAxisMaxDateInput,
],
[ChartType.Bar]: [
dataDirectionOption,

View File

@@ -18,7 +18,7 @@ export class ChartTab implements IPanelTab {
public readonly identifier = 'ChartTab';
public readonly view: ChartView;
constructor(@IInstantiationService instantiationService: IInstantiationService) {
constructor( @IInstantiationService instantiationService: IInstantiationService) {
this.view = instantiationService.createInstance(ChartView);
}
@@ -26,7 +26,11 @@ export class ChartTab implements IPanelTab {
this.view.queryRunner = runner;
}
public chart(dataId: { batchId: number, resultId: number}): void {
public chart(dataId: { batchId: number, resultId: number }): void {
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 { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
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 { IThemeService } from 'vs/platform/theme/common/themeService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -43,7 +43,7 @@ declare class Proxy {
const insightRegistry = Registry.as<IInsightRegistry>(Extensions.InsightContribution);
export class ChartView implements IPanelView {
export class ChartView extends Disposable implements IPanelView {
private insight: Insight;
private _queryRunner: QueryRunner;
private _data: IInsightData;
@@ -82,6 +82,7 @@ export class ChartView implements IPanelView {
@IInstantiationService private _instantiationService: IInstantiationService,
@IContextMenuService contextMenuService: IContextMenuService
) {
super();
this.taskbarContainer = $('div.taskbar-container');
this.taskbar = new Taskbar(this.taskbarContainer, contextMenuService);
this.optionsControl = $('div.options-container');
@@ -324,6 +325,24 @@ export class ChartView implements IPanelView {
};
this.optionDisposables.push(attachInputBoxStyler(numberInput, this._themeService));
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 };
container.appendChild(optionContainer);

View File

@@ -7,7 +7,7 @@
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 * as colors from 'vs/platform/theme/common/colorRegistry';
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 { 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 timeSeriesScales = {
scales: {
xAxes: [{
type: 'time',
display: true,
ticks: {
autoSkip: false,
maxRotation: 45,
minRotation: 45
}
}],
yAxes: [{
display: true,
}]
}
};
const defaultOptions: IInsightOptions = {
type: ChartType.Bar,
dataDirection: DataDirection.Horizontal
@@ -30,6 +48,8 @@ export class Graph implements IInsight {
private chartjs: ChartJs;
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 readonly types = Graph.types;
@@ -83,37 +103,51 @@ export class Graph implements IInsight {
labels = data.rows.map(row => row[0]);
}
if (this.options.dataDirection === DataDirection.Horizontal) {
if (this.options.labelFirstColumn) {
chartData = data.rows.map((row) => {
return {
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)
};
});
}
if (this.originalType === ChartType.TimeSeries) {
let dataSetMap: { [label: string]: IPointDataSet } = {};
this._data.rows.map(row => {
if (row && row.length >= 3) {
let legend = row[0];
if (!dataSetMap[legend]) {
dataSetMap[legend] = { label: legend, data: [], fill: false };
}
dataSetMap[legend].data.push({ x: row[1], y: Number(row[2]) });
}
});
chartData = Object.values(dataSetMap);
} else {
if (this.options.columnsAsLabels) {
chartData = data.rows[0].slice(1).map((row, i) => {
return {
data: data.rows.map(row => Number(row[i + 1])),
label: data.columns[i + 1]
};
});
if (this.options.dataDirection === DataDirection.Horizontal) {
if (this.options.labelFirstColumn) {
chartData = data.rows.map((row) => {
return {
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 {
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)
};
});
if (this.options.columnsAsLabels) {
chartData = data.rows[0].slice(1).map((row, i) => {
return {
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
}
}];
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>{
@@ -208,6 +271,12 @@ export class Graph implements IInsight {
public set options(options: IInsightOptions) {
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;
}

View File

@@ -7,7 +7,7 @@
import { Graph } from './graphInsight';
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 { TableInsight } from './tableInsight';
import { IInsightOptions, IInsight, InsightType, IInsightCtor } from './interfaces';
@@ -16,6 +16,7 @@ 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';
import { deepClone } from 'vs/base/common/objects';
const defaultOptions: IInsightOptions = {
type: ChartType.Bar,
@@ -47,13 +48,13 @@ export class Insight {
}
public set options(val: IInsightOptions) {
this._options = val;
this._options = deepClone(val);
if (this.insight) {
// 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();
} 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 * as sqlops from 'sqlops';
import * as pretty from 'pretty-data';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
@@ -41,13 +42,14 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IAction } from 'vs/base/common/actions';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
const ROW_HEIGHT = 29;
const HEADER_HEIGHT = 26;
const MIN_GRID_HEIGHT_ROWS = 8;
const ESTIMATED_SCROLL_BAR_HEIGHT = 10;
const BOTTOM_PADDING = 15;
const ACTIONBAR_WIDTH = 26;
const ACTIONBAR_WIDTH = 36;
// minimum height needed to show the full actionbar
const ACTIONBAR_HEIGHT = 100;
@@ -133,7 +135,7 @@ export class GridPanel extends ViewletPanel {
@IInstantiationService private instantiationService: IInstantiationService
) {
super(options, keybindingService, contextMenuService, configurationService);
this.splitView = new ScrollableSplitView(this.container, { enableResizing: false });
this.splitView = new ScrollableSplitView(this.container, { enableResizing: false, verticalScrollbarVisibility: ScrollbarVisibility.Visible });
this.splitView.onScroll(e => {
if (this.state) {
this.state.scrollPosition = e;
@@ -477,7 +479,27 @@ class GridTable<T> extends Disposable implements IView {
if (column && (column.isXml || column.isJson)) {
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 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);
});
}

View File

@@ -18,9 +18,9 @@ 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, 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 gridPanel: GridPanel;
private messagePanel: MessagePanel;
@@ -30,10 +30,10 @@ class ResultsView implements IPanelView {
private _state: ResultsViewState;
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' });
super();
this.panelViewlet = this._register(this.instantiationService.createInstance(PanelViewlet, 'resultsView', { showHeaderInTitleWhenSingleView: false }));
this.gridPanel = this._register(this.instantiationService.createInstance(GridPanel, { title: nls.localize('gridPanel', 'Results'), id: 'gridPanel' }));
this.messagePanel = this._register(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(() => {
@@ -147,9 +147,13 @@ class ResultsTab implements IPanelTab {
public set queryRunner(runner: QueryRunner) {
this.view.queryRunner = runner;
}
public dispose() {
dispose(this.view);
}
}
export class QueryResultsView {
export class QueryResultsView extends Disposable {
private _panelView: TabbedPanel;
private _input: QueryResultsInput;
private resultsTab: ResultsTab;
@@ -163,16 +167,17 @@ export class QueryResultsView {
@IInstantiationService instantiationService: IInstantiationService,
@IQueryModelService private queryModelService: IQueryModelService
) {
this.resultsTab = new ResultsTab(instantiationService);
this.chartTab = new ChartTab(instantiationService);
this._panelView = new TabbedPanel(container, { showHeaderWhenSingleView: false });
this.qpTab = new QueryPlanTab();
super();
this.resultsTab = this._register(new ResultsTab(instantiationService));
this.chartTab = this._register(new ChartTab(instantiationService));
this._panelView = this._register(new TabbedPanel(container, { showHeaderWhenSingleView: false }));
this.qpTab = this._register(new QueryPlanTab());
this._panelView.pushTab(this.resultsTab);
this._panelView.onTabChange(e => {
this._register(this._panelView.onTabChange(e => {
if (this.input) {
this.input.state.activeTab = e;
}
});
}));
}
public style() {

View File

@@ -14,6 +14,7 @@ import { QueryInput } from 'sql/parts/query/common/queryInput';
import { QueryStatusbarItem } from 'sql/parts/query/execution/queryStatus';
import { SqlFlavorStatusbarItem } from 'sql/parts/query/common/flavorStatus';
import { RowCountStatusBarItem } from 'sql/parts/query/common/rowCountStatus';
import { TimeElapsedStatusBarItem } from 'sql/parts/query/common/timeElapsedStatus';
import * as sqlops from 'sqlops';
@@ -86,6 +87,12 @@ export class QueryModelService implements IQueryModelService {
// Register Statusbar items
(<statusbar.IStatusbarRegistry>platform.Registry.as(statusbar.Extensions.Statusbar)).registerStatusbarItem(new statusbar.StatusbarItemDescriptor(
TimeElapsedStatusBarItem,
statusbar.StatusbarAlignment.RIGHT,
100 /* Should appear to the right of the SQL editor status */
));
(<statusbar.IStatusbarRegistry>platform.Registry.as(statusbar.Extensions.Statusbar)).registerStatusbarItem(new statusbar.StatusbarItemDescriptor(
RowCountStatusBarItem,
statusbar.StatusbarAlignment.RIGHT,
@@ -352,7 +359,7 @@ export class QueryModelService implements IQueryModelService {
public disposeQuery(ownerUri: string): void {
// Get existing query runner
let queryRunner = this.getQueryRunner(ownerUri);
let queryRunner = this.internalGetQueryRunner(ownerUri);
if (queryRunner) {
queryRunner.disposeQuery();
}
@@ -444,7 +451,7 @@ export class QueryModelService implements IQueryModelService {
public disposeEdit(ownerUri: string): Thenable<void> {
// Get existing query runner
let queryRunner = this.getQueryRunner(ownerUri);
let queryRunner = this.internalGetQueryRunner(ownerUri);
if (queryRunner) {
return queryRunner.disposeEdit(ownerUri);
}
@@ -453,7 +460,7 @@ export class QueryModelService implements IQueryModelService {
public updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable<sqlops.EditUpdateCellResult> {
// Get existing query runner
let queryRunner = this.getQueryRunner(ownerUri);
let queryRunner = this.internalGetQueryRunner(ownerUri);
if (queryRunner) {
return queryRunner.updateCell(ownerUri, rowId, columnId, newValue).then((result) => result, error => {
this._notificationService.notify({
@@ -468,7 +475,7 @@ export class QueryModelService implements IQueryModelService {
public commitEdit(ownerUri): Thenable<void> {
// Get existing query runner
let queryRunner = this.getQueryRunner(ownerUri);
let queryRunner = this.internalGetQueryRunner(ownerUri);
if (queryRunner) {
return queryRunner.commitEdit(ownerUri).then(() => { }, error => {
this._notificationService.notify({
@@ -483,7 +490,7 @@ export class QueryModelService implements IQueryModelService {
public createRow(ownerUri: string): Thenable<sqlops.EditCreateRowResult> {
// Get existing query runner
let queryRunner = this.getQueryRunner(ownerUri);
let queryRunner = this.internalGetQueryRunner(ownerUri);
if (queryRunner) {
return queryRunner.createRow(ownerUri);
}
@@ -492,7 +499,7 @@ export class QueryModelService implements IQueryModelService {
public deleteRow(ownerUri: string, rowId: number): Thenable<void> {
// Get existing query runner
let queryRunner = this.getQueryRunner(ownerUri);
let queryRunner = this.internalGetQueryRunner(ownerUri);
if (queryRunner) {
return queryRunner.deleteRow(ownerUri, rowId);
}
@@ -501,7 +508,7 @@ export class QueryModelService implements IQueryModelService {
public revertCell(ownerUri: string, rowId: number, columnId: number): Thenable<sqlops.EditRevertCellResult> {
// Get existing query runner
let queryRunner = this.getQueryRunner(ownerUri);
let queryRunner = this.internalGetQueryRunner(ownerUri);
if (queryRunner) {
return queryRunner.revertCell(ownerUri, rowId, columnId);
}
@@ -510,16 +517,25 @@ export class QueryModelService implements IQueryModelService {
public revertRow(ownerUri: string, rowId: number): Thenable<void> {
// Get existing query runner
let queryRunner = this.getQueryRunner(ownerUri);
let queryRunner = this.internalGetQueryRunner(ownerUri);
if (queryRunner) {
return queryRunner.revertRow(ownerUri, rowId);
}
return TPromise.as(null);
}
public getQueryRunner(ownerUri): QueryRunner {
let queryRunner: QueryRunner = undefined;
if (this._queryInfoMap.has(ownerUri)) {
queryRunner = this._getQueryInfo(ownerUri).queryRunner;
}
// return undefined if not found or is already executing
return queryRunner;
}
// PRIVATE METHODS //////////////////////////////////////////////////////
public getQueryRunner(ownerUri): QueryRunner {
private internalGetQueryRunner(ownerUri): QueryRunner {
let queryRunner: QueryRunner = undefined;
if (this._queryInfoMap.has(ownerUri)) {
let existingRunner = this._getQueryInfo(ownerUri).queryRunner;

View File

@@ -112,6 +112,15 @@ export default class QueryRunner {
private _onBatchEnd = new Emitter<sqlops.BatchSummary>();
public readonly onBatchEnd: Event<sqlops.BatchSummary> = this._onBatchEnd.event;
private _queryStartTime: Date;
public get queryStartTime(): Date {
return this._queryStartTime;
}
private _queryEndTime: Date;
public get queryEndTime(): Date {
return this._queryEndTime;
}
// CONSTRUCTOR /////////////////////////////////////////////////////////
constructor(
public uri: string,
@@ -185,9 +194,10 @@ export default class QueryRunner {
this._debouncedMessage.clear();
this._debouncedResultSet.clear();
this._planXml = new Deferred<string>();
let ownerUri = this.uri;
this._batchSets = [];
this._hasCompleted = false;
this._queryStartTime = undefined;
this._queryEndTime = undefined;
if (types.isObject(input) || types.isUndefinedOrNull(input)) {
// Update internal state to show that we're executing the query
this._resultLineOffset = input ? input.startLine : 0;
@@ -203,20 +213,22 @@ export default class QueryRunner {
// Send the request to execute the query
return runCurrentStatement
? this._queryManagementService.runQueryStatement(ownerUri, input.startLine, input.startColumn).then(() => this.handleSuccessRunQueryResult(), e => this.handleFailureRunQueryResult(e))
: this._queryManagementService.runQuery(ownerUri, input, runOptions).then(() => this.handleSuccessRunQueryResult(), e => this.handleFailureRunQueryResult(e));
? this._queryManagementService.runQueryStatement(this.uri, input.startLine, input.startColumn).then(() => this.handleSuccessRunQueryResult(), e => this.handleFailureRunQueryResult(e))
: this._queryManagementService.runQuery(this.uri, input, runOptions).then(() => this.handleSuccessRunQueryResult(), e => this.handleFailureRunQueryResult(e));
} else if (types.isString(input)) {
// Update internal state to show that we're executing the query
this._isExecuting = true;
this._totalElapsedMilliseconds = 0;
return this._queryManagementService.runQueryString(ownerUri, input).then(() => this.handleSuccessRunQueryResult(), e => this.handleFailureRunQueryResult(e));
return this._queryManagementService.runQueryString(this.uri, input).then(() => this.handleSuccessRunQueryResult(), e => this.handleFailureRunQueryResult(e));
} else {
return Promise.reject('Unknown input');
}
}
private handleSuccessRunQueryResult() {
// this isn't exact, but its the best we can do
this._queryStartTime = new Date();
// The query has started, so lets fire up the result pane
this._onQueryStart.fire();
this._eventEmitter.emit(EventType.START);
@@ -241,6 +253,8 @@ export default class QueryRunner {
* Handle a QueryComplete from the service layer
*/
public handleQueryComplete(result: sqlops.QueryExecuteCompleteNotificationResult): void {
// this also isn't exact but its the best we can do
this._queryEndTime = new Date();
// Store the batch sets we got back as a source of "truth"
this._isExecuting = false;

View File

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

View File

@@ -41,13 +41,13 @@ export class DialogModal extends Modal {
name: string,
options: IModalOptions,
@IPartService partService: IPartService,
@IWorkbenchThemeService private _themeService: IWorkbenchThemeService,
@IWorkbenchThemeService themeService: IWorkbenchThemeService,
@ITelemetryService telemetryService: ITelemetryService,
@IContextKeyService contextKeyService: IContextKeyService,
@IClipboardService clipboardService: IClipboardService,
@IInstantiationService private _instantiationService: IInstantiationService
) {
super(_dialog.title, name, partService, telemetryService, clipboardService, contextKeyService, options);
super(_dialog.title, name, partService, telemetryService, clipboardService, themeService, contextKeyService, options);
}
public layout(): void {

View File

@@ -21,7 +21,8 @@
flex-direction: column;
width: 100%;
height: 100%;
overflow: scroll;
overflow-x: hidden;
overflow-y: scroll;
}
.dialogModal-hidden {

View File

@@ -46,13 +46,13 @@ export class WizardModal extends Modal {
name: string,
options: IModalOptions,
@IPartService partService: IPartService,
@IWorkbenchThemeService private _themeService: IWorkbenchThemeService,
@IWorkbenchThemeService themeService: IWorkbenchThemeService,
@ITelemetryService telemetryService: ITelemetryService,
@IContextKeyService contextKeyService: IContextKeyService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IClipboardService clipboardService: IClipboardService
) {
super(_wizard.title, name, partService, telemetryService, clipboardService, contextKeyService, options);
super(_wizard.title, name, partService, telemetryService, clipboardService, themeService, contextKeyService, options);
}
public layout(): void {

View File

@@ -42,13 +42,13 @@ export class ErrorMessageDialog extends Modal {
public onOk: Event<void> = this._onOk.event;
constructor(
@IThemeService private _themeService: IThemeService,
@IThemeService themeService: IThemeService,
@IClipboardService clipboardService: IClipboardService,
@IPartService partService: IPartService,
@ITelemetryService telemetryService: ITelemetryService,
@IContextKeyService contextKeyService: IContextKeyService
) {
super('', TelemetryKeys.ErrorMessage, partService, telemetryService, clipboardService, contextKeyService, { isFlyout: false, hasTitleIcon: true });
super('', TelemetryKeys.ErrorMessage, partService, telemetryService, clipboardService, themeService, contextKeyService, { isFlyout: false, hasTitleIcon: true });
this._okLabel = localize('errorMessageDialog.ok', 'OK');
this._closeLabel = localize('errorMessageDialog.close', 'Close');
}