SQL Operations Studio Public Preview 1 (0.23) release source code

This commit is contained in:
Karl Burtram
2017-11-09 14:30:27 -08:00
parent b88ecb8d93
commit 3cdac41339
8829 changed files with 759707 additions and 286 deletions

View File

@@ -0,0 +1,137 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { EditorDescriptor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { Registry } from 'vs/platform/registry/common/platform';
import { IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import * as nls from 'vs/nls';
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
import { ProfilerEditor } from 'sql/parts/profiler/editor/profilerEditor';
import { PROFILER_SESSION_TEMPLATE_SETTINGS, IProfilerSessionTemplate } from 'sql/parts/profiler/service/interfaces';
const profilerDescriptor = new EditorDescriptor(
ProfilerEditor.ID,
'Profiler',
'sql/parts/profiler/editor/profilerEditor',
'ProfilerEditor'
);
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(profilerDescriptor, [new SyncDescriptor(ProfilerInput)]);
const profilerSessionTemplateSchema: IJSONSchema = {
description: nls.localize('profiler.settings.sessionTemplates', "Specifies session templates"),
type: 'array',
items: <IJSONSchema>{
type: 'object',
properties: {
name: {
type: 'string'
}
}
},
default: <Array<IProfilerSessionTemplate>>[
{
name: 'Standard',
events: [
{
name: 'Audit Login',
optionalColumns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'ClientProcessID', 'SPID', 'StartTime', 'BinaryData']
},
{
name: 'Audit Logout',
optionalColumns: ['ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime']
},
{
name: 'ExistingConnection',
optionalColumns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime', 'BinaryData']
},
{
name: 'RPC:Completed',
optionalColumns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime', 'BinaryData']
},
{
name: 'SQL:BatchCompleted',
optionalColumns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime', 'BinaryData']
},
{
name: 'SQL:BatchStarting',
optionalColumns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'ClientProcessID', 'SPID', 'StartTime']
}
],
view: {
events: [
{
name: 'Audit Login',
columns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'ClientProcessID', 'SPID', 'StartTime']
},
{
name: 'Audit Logout',
columns: ['ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime']
},
{
name: 'ExistingConnection',
columns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'ClientProcessID', 'SPID', 'StartTime']
},
{
name: 'RPC:Completed',
columns: ['ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime', 'BinaryData']
},
{
name: 'SQL:BatchCompleted',
columns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime', 'BinaryData']
},
{
name: 'SQL:BatchStarting',
columns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'ClientProcessID', 'SPID', 'StartTime']
}
]
}
},
{
name: 'TSQL'
},
{
name: 'Blank'
},
{
name: 'SP_Counts'
},
{
name: 'TQL_Duration'
},
{
name: 'TSQL_Grouped'
},
{
name: 'TSQL_Locks'
},
{
name: 'TSQL_Replay'
},
{
name: 'TSQL_SPs'
},
{
name: 'Tuning'
}
]
};
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
const dashboardConfig: IConfigurationNode = {
id: 'Profiler',
type: 'object',
properties: {
[PROFILER_SESSION_TEMPLATE_SETTINGS]: profilerSessionTemplateSchema
}
};
configurationRegistry.registerConfiguration(dashboardConfig);

View File

@@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { GlobalNewProfilerAction } from './profilerWorkbenchActions';
import { registerTask } from 'sql/platform/tasks/taskRegistry';
import { NewProfilerAction } from './profilerActions';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import * as nls from 'vs/nls';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
// Contribute Global Actions
const category = nls.localize('profilerCategory', "Profiler");
const newProfilerSchema: IJSONSchema = {
description: nls.localize('carbon.actions.newProfiler', 'Open up a new profiler window'),
type: 'null',
default: null
};
if (process.env['VSCODE_DEV']) {
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalNewProfilerAction, GlobalNewProfilerAction.ID, GlobalNewProfilerAction.LABEL), 'Profiler: New Profiler', category);
registerTask('new-profiler', '', newProfilerSchema, NewProfilerAction);
}

View File

@@ -0,0 +1,244 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
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';
import { ITaskActionContext, TaskAction } from 'sql/workbench/common/actions';
import { TPromise } from 'vs/base/common/winjs.base';
import { Action } from 'vs/base/common/actions';
import * as nls from 'vs/nls';
import { IEditorAction } from 'vs/editor/common/editorCommon';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export class ProfilerConnect extends Action {
public static ID = 'profiler.connect';
public static LABEL = nls.localize('connect', "Connect");
private _connected: boolean = false;
constructor(
id: string, label: string,
@IProfilerService private _profilerService: IProfilerService
) {
super(id, label, 'connect');
}
public run(input: ProfilerInput): TPromise<boolean> {
this.enabled = false;
if (!this._connected) {
return TPromise.wrap(this._profilerService.connectSession(input.id).then(() => {
this.enabled = true;
this.connected = true;
input.state.change({ isConnected: true, isRunning: false, isPaused: false, isStopped: true });
return true;
}));
} else {
return TPromise.wrap(this._profilerService.disconnectSession(input.id).then(() => {
this.enabled = true;
this.connected = false;
input.state.change({ isConnected: false, isRunning: false, isPaused: false, isStopped: false });
return true;
}));
}
}
public set connected(value: boolean) {
this._connected = value;
this._setClass(value ? 'disconnect' : 'connect');
this._setLabel(value ? nls.localize('disconnect', 'Disconnected') : nls.localize('connect', "Connect"));
}
public get connected(): boolean {
return this._connected;
}
}
export class ProfilerStart extends Action {
public static ID = 'profiler.start';
public static LABEL = nls.localize('start', "Start");
constructor(
id: string, label: string,
@IProfilerService private _profilerService: IProfilerService
) {
super(id, label, 'start');
}
public run(input: ProfilerInput): TPromise<boolean> {
this.enabled = false;
return TPromise.wrap(this._profilerService.startSession(input.id).then(() => {
input.state.change({ isRunning: true, isStopped: false, isPaused: false });
return true;
}));
}
}
export class ProfilerPause extends Action {
public static ID = 'profiler.pause';
public static LABEL = nls.localize('pause', "Pause");
constructor(
id: string, label: string,
@IProfilerService private _profilerService: IProfilerService
) {
super(id, label, 'stop');
}
public run(input: ProfilerInput): TPromise<boolean> {
this.enabled = false;
return TPromise.wrap(this._profilerService.pauseSession(input.id).then(() => {
input.state.change({ isPaused: true, isStopped: false, isRunning: false });
return true;
}));
}
}
export class ProfilerStop extends Action {
public static ID = 'profiler.stop';
public static LABEL = nls.localize('stop', "Stop");
constructor(
id: string, label: string,
@IProfilerService private _profilerService: IProfilerService
) {
super(id, label, 'stop');
}
public run(input: ProfilerInput): TPromise<boolean> {
this.enabled = false;
return TPromise.wrap(this._profilerService.stopSession(input.id).then(() => {
input.state.change({ isStopped: true, isPaused: false, isRunning: false });
return true;
}));
}
}
export class ProfilerClear extends Action {
public static ID = 'profiler.clear';
public static LABEL = nls.localize('profiler.clear', "Clear Data");
constructor(id: string, label: string) {
super(id, label, 'stop');
}
run(input: ProfilerInput): TPromise<void> {
input.data.clear();
return TPromise.as(null);
}
}
export class ProfilerAutoScroll extends Action {
public static ID = 'profiler.autoscroll';
public static LABEL = nls.localize('profiler.toggleAutoscroll', "Toggle Auto Scroll");
constructor(id: string, label: string) {
super(id, label, 'stop');
}
run(input: ProfilerInput): TPromise<boolean> {
this.checked = !this.checked;
input.state.change({ autoscroll: this.checked });
return TPromise.as(true);
}
}
export class ProfilerCollapsablePanelAction extends Action {
public static ID = 'profiler.toggleCollapsePanel';
public static LABEL = nls.localize('profiler.toggleCollapsePanel', "Toggle Collapsed Panel");
private _collapsed: boolean;
constructor(id: string, label: string) {
super(id, label, 'minimize-panel-action');
}
public run(input: ProfilerInput): TPromise<boolean> {
this.collapsed = !this._collapsed;
input.state.change({ isPanelCollapsed: this._collapsed });
return TPromise.as(true);
}
set collapsed(val: boolean) {
this._collapsed = val === false ? false : true;
this._setClass(this._collapsed ? 'maximize-panel-action' : 'minimize-panel-action');
}
}
export class ProfilerEditColumns extends Action {
public static ID = 'profiler.';
public static LABEL = nls.localize('profiler.editColumns', "Edit Columns");
constructor(
id: string, label: string,
@IProfilerService private _profilerService: IProfilerService
) {
super(id, label);
}
public run(input: ProfilerInput): TPromise<boolean> {
return TPromise.wrap(this._profilerService.launchColumnEditor(input)).then(() => true);
}
}
export class ProfilerFindNext implements IEditorAction {
public readonly id = 'profiler.findNext';
public readonly label = nls.localize('profiler.findNext', "Find Next String");
public readonly alias = '';
constructor(private profiler: IProfilerController) { }
run(): TPromise<void> {
this.profiler.findNext();
return TPromise.as(null);
}
isSupported(): boolean {
return true;
}
}
export class ProfilerFindPrevious implements IEditorAction {
public readonly id = 'profiler.findPrevious';
public readonly label = nls.localize('profiler.findPrevious', "Find Previous String");
public readonly alias = '';
constructor(private profiler: IProfilerController) { }
run(): TPromise<void> {
this.profiler.findPrevious();
return TPromise.as(null);
}
isSupported(): boolean {
return true;
}
}
export class NewProfilerAction extends TaskAction {
public static ID = 'newProfiler';
public static LABEL = nls.localize('newProfiler', 'New Profiler');
public static ICON = 'profile';
constructor(
id: string, label: string, icon: string,
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService,
@IInstantiationService private _instantiationService: IInstantiationService
) {
super(id, label, icon);
}
run(actionContext: ITaskActionContext): TPromise<boolean> {
let profilerInput = this._instantiationService.createInstance(ProfilerInput, actionContext.profile);
return this._editorService.openEditor(profilerInput, { pinned: true }, false).then(() => {
return TPromise.as(true);
});
}
}

View File

@@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
import { TPromise } from 'vs/base/common/winjs.base';
import { Action } from 'vs/base/common/actions';
import * as nls from 'vs/nls';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export class GlobalNewProfilerAction extends Action {
public static ID = 'explorer.newProfiler';
public static LABEL = nls.localize('newProfiler', "New Profiler");
constructor(
id: string, label: string,
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService,
@IInstantiationService private _instantiationService: IInstantiationService
) {
super(id, label);
}
run(context?: any): TPromise<boolean> {
let profilerInput = this._instantiationService.createInstance(ProfilerInput, context ? context.connectionProfile : undefined);
return this._editorService.openEditor(profilerInput, { pinned: true }, false).then(() => TPromise.as(true));
}
}

View File

@@ -0,0 +1,13 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.profiler-column-tree {
height: calc(100% - 25px);
width: 100%;
}
.tree-row > * {
display: inline-block;
}

View File

@@ -0,0 +1,409 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./media/profilerDialog';
import { Modal } from 'sql/base/browser/ui/modal/modal';
import { attachModalDialogStyler } from 'sql/common/theme/styler';
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
import * as TelemetryKeys from 'sql/common/telemetryKeys';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import * as nls from 'vs/nls';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { Builder } from 'vs/base/browser/builder';
import { SelectBox } from 'vs/base/browser/ui/selectBox/selectBox';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import * as DOM from 'vs/base/browser/dom';
import { IDataSource, ITree, IRenderer } from 'vs/base/parts/tree/browser/tree';
import { TPromise } from 'vs/base/common/winjs.base';
import { attachListStyler } from 'vs/platform/theme/common/styler';
import Event, { Emitter } from 'vs/base/common/event';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
class EventItem {
constructor(
private _name: string,
private _parent: SessionItem,
private _columns?: Array<ColumnItem>,
) {
if (!_columns) {
this._columns = new Array<ColumnItem>();
}
}
public hasChildren(): boolean {
return this._columns && this._columns.length > 0;
}
public getChildren(): Array<ColumnItem> {
return this._columns;
}
public get id(): string {
return this._name;
}
public addColumn(...columns: Array<ColumnItem>) {
this._columns = this._columns.concat(columns);
}
public get parent(): SessionItem {
return this._parent;
}
public get selected(): boolean {
return this._columns.every(i => i.selected);
}
public set selected(val: boolean) {
this._columns.forEach(i => i.selected = val);
}
public get indeterminate(): boolean {
return this._columns.some(i => i.selected) && !this.selected;
}
}
class ColumnItem {
public selected: boolean;
public readonly indeterminate = false;
constructor(
private _name: string,
private _parent: EventItem
) { }
public get id(): string {
return this._name;
}
public get parent(): EventItem {
return this._parent;
}
}
class ColumnSortedColumnItem {
constructor(
private _name: string,
private _parent: SessionItem
) { }
public get id(): string {
return this._name;
}
public get parent(): SessionItem {
return this._parent;
}
public get selected(): boolean {
return this._parent.getUnsortedChildren()
.every(e => e.getChildren().filter(c => c.id === this.id)
.every(c => c.selected));
}
public set selected(val: boolean) {
this._parent.getUnsortedChildren()
.forEach(e => e.getChildren()
.filter(c => c.id === this.id)
.forEach(c => c.selected = val));
}
public get indeterminate(): boolean {
return this._parent.getUnsortedChildren()
.some(e => e.getChildren()
.filter(c => c.id === this.id)
.some(c => c.selected))
&& !this.selected;
}
}
class SessionItem {
private _sortedColumnItems: Array<ColumnSortedColumnItem> = [];
constructor(
private _name: string,
private _sort: 'event' | 'column',
private _events?: Array<EventItem>
) {
if (!_events) {
this._events = new Array<EventItem>();
} else {
_events.forEach(e => {
e.getChildren().forEach(c => {
if (!this._sortedColumnItems.some(i => i.id === c.id)) {
this._sortedColumnItems.push(new ColumnSortedColumnItem(c.id, this));
}
});
});
}
}
public get id(): string {
return this._name;
}
public hasChildren(): boolean {
if (this._sort === 'event') {
return this._events && this._events.length > 0;
} else {
return this._events && this._events.some(i => i.hasChildren());
}
}
public getUnsortedChildren(): Array<EventItem> {
return this._events;
}
public getChildren(): Array<EventItem | ColumnSortedColumnItem> {
if (this._sort === 'event') {
return this._events;
} else {
return this._sortedColumnItems;
}
}
public addEvents(...events: Array<EventItem>) {
this._events = this._events.concat(events);
events.forEach(e => {
e.getChildren().forEach(c => {
if (!this._sortedColumnItems.some(i => i.id === c.id)) {
this._sortedColumnItems.push(new ColumnSortedColumnItem(c.id, this));
}
});
});
}
public changeSort(type: 'event' | 'column'): void {
this._sort = type;
}
}
class TreeRenderer implements IRenderer {
private _onSelectedChange = new Emitter<any>();
public onSelectedChange: Event<any> = this._onSelectedChange.event;
getHeight(tree: ITree, element: any): number {
return 22;
}
getTemplateId(tree: ITree, element: any): string {
if (element instanceof SessionItem) {
return 'session';
} else if (element instanceof EventItem) {
return 'event';
} else if (element instanceof ColumnItem) {
return 'column';
} else if (element instanceof ColumnSortedColumnItem) {
return 'columnSorted';
} else {
return undefined;
}
}
renderTemplate(tree: ITree, templateId: string, container: HTMLElement): RenderTemplate {
let data = Object.create(null);
let row = document.createElement('div');
row.className = 'tree-row';
DOM.append(container, row);
data.toDispose = [];
data.checkbox = document.createElement('input');
DOM.append(row, data.checkbox);
data.checkbox.type = 'checkbox';
data.toDispose.push(DOM.addStandardDisposableListener(data.checkbox, 'change', () => {
data.context.selected = !data.context.selected;
this._onSelectedChange.fire(data.context);
}));
data.label = document.createElement('div');
DOM.append(row, data.label);
return data;
}
renderElement(tree: ITree, element: any, templateId: string, templateData: RenderTemplate): void {
templateData.context = element;
templateData.label.innerText = element.id;
templateData.checkbox.checked = element.selected;
templateData.checkbox.indeterminate = element.indeterminate;
}
disposeTemplate(tree: ITree, templateId: string, templateData: RenderTemplate): void {
dispose(templateData.toDispose);
}
}
interface RenderTemplate {
label: HTMLElement;
toDispose: Array<IDisposable>;
checkbox: HTMLInputElement;
context?: any;
}
class TreeDataSource implements IDataSource {
getId(tree: ITree, element: any): string {
if (element instanceof EventItem) {
return element.parent.id + element.id;
} else if (element instanceof ColumnItem) {
return element.parent.parent.id + element.parent.id + element.id;
} else if (element instanceof SessionItem) {
return element.id;
} else if (element instanceof ColumnSortedColumnItem) {
return element.id;
} else {
return undefined;
}
}
hasChildren(tree: ITree, element: any): boolean {
if (element instanceof SessionItem) {
return element.hasChildren();
} else if (element instanceof EventItem) {
return element.hasChildren();
} else {
return undefined;
}
}
getChildren(tree: ITree, element: any): TPromise<Array<any>> {
if (element instanceof EventItem) {
return TPromise.as(element.getChildren());
} else if (element instanceof SessionItem) {
return TPromise.as(element.getChildren());
} else {
return TPromise.as(null);
}
}
getParent(tree: ITree, element: any): TPromise<any> {
if (element instanceof ColumnItem) {
return TPromise.as(element.parent);
} else if (element instanceof EventItem) {
return TPromise.as(element.parent);
} else if (element instanceof ColumnSortedColumnItem) {
return TPromise.as(element.parent);
} else {
return TPromise.as(null);
}
}
shouldAutoexpand?(tree: ITree, element: any): boolean {
return false;
}
}
export class ProfilerColumnEditorDialog extends Modal {
private _selectBox: SelectBox;
private _selectedValue: number = 0;
private readonly _options = [
nls.localize('eventSort', "Sort by event"),
nls.localize('nameColumn', "Sort by column")
];
private _tree: Tree;
private _input: ProfilerInput;
private _element: SessionItem;
private _treeContainer: HTMLElement;
constructor(
@IPartService _partService: IPartService,
@IThemeService private _themeService: IThemeService,
@ITelemetryService telemetryService: ITelemetryService,
@IContextKeyService contextKeyService: IContextKeyService
) {
super(nls.localize('profiler', 'Profiler'), TelemetryKeys.Profiler, _partService, telemetryService, contextKeyService);
}
public render(): void {
super.render();
this._register(attachModalDialogStyler(this, this._themeService));
this.addFooterButton(nls.localize('ok', "OK"), () => this.onAccept(undefined));
this.addFooterButton(nls.localize('cancel', "Cancel"), () => this.onClose(undefined));
}
protected renderBody(container: HTMLElement): void {
let builder = new Builder(container);
builder.div({}, b => {
this._selectBox = new SelectBox(this._options, 0);
this._selectBox.render(b.getHTMLElement());
this._register(this._selectBox.onDidSelect(e => {
this._selectedValue = e.index;
this._element.changeSort(e.index === 0 ? 'event' : 'column');
this._tree.refresh(this._element, true);
}));
});
builder.div({ 'class': 'profiler-column-tree' }, b => {
this._treeContainer = b.getHTMLElement();
let renderer = new TreeRenderer();
this._tree = new Tree(this._treeContainer, { dataSource: new TreeDataSource(), renderer });
this._register(renderer.onSelectedChange(e => this._tree.refresh(e, true)));
this._register(attachListStyler(this._tree, this._themeService));
});
}
public open(input: ProfilerInput): void {
super.show();
this._input = input;
this._updateList();
}
protected onAccept(e: StandardKeyboardEvent): void {
this._updateInput();
super.onAccept(e);
}
private _updateInput(): void {
this._element.getUnsortedChildren().forEach(e => {
let origEvent = this._input.sessionTemplate.view.events.find(i => i.name === e.id);
if (e.indeterminate) {
e.getChildren().forEach(c => {
if (origEvent.columns.includes(c.id) && !c.selected) {
origEvent.columns = origEvent.columns.filter(i => i !== c.id);
} else if (!origEvent.columns.includes(c.id) && c.selected) {
origEvent.columns.push(c.id);
}
});
} else {
origEvent.columns = e.getChildren()
.filter(c => c.selected)
.map(c => c.id);
}
});
let newColumns = this._input.sessionTemplate.view.events.reduce<Array<string>>((p, e) => {
e.columns.forEach(c => {
if (!p.includes(c)) {
p.push(c);
}
});
return p;
}, []);
newColumns.unshift('EventClass');
this._input.setColumns(newColumns);
}
private _updateList(): void {
this._element = new SessionItem(this._input.sessionTemplate.name, this._selectedValue === 0 ? 'event' : 'column');
this._input.sessionTemplate.events.forEach(item => {
let event = new EventItem(item.name, this._element);
item.optionalColumns.forEach(col => {
let column = new ColumnItem(col, event);
column.selected = this._input.sessionTemplate.view.events.find(i => i.name === event.id).columns.includes(col);
event.addColumn(column);
});
this._element.addEvents(event);
});
this._tree.setInput(this._element);
this._tree.layout(DOM.getTotalHeight(this._treeContainer));
}
protected layout(height?: number): void {
this._tree.layout(DOM.getContentHeight(this._treeContainer));
}
}

View File

@@ -0,0 +1,9 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export interface IProfilerController {
findNext(): void;
findPrevious(): void;
}

View File

@@ -0,0 +1,647 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!vs/editor/contrib/find/browser/findWidget';
import * as nls from 'vs/nls';
import { onUnexpectedError } from 'vs/base/common/errors';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import * as platform from 'vs/base/common/platform';
import * as strings from 'vs/base/common/strings';
import * as dom from 'vs/base/browser/dom';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput';
import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox';
import { Widget } from 'vs/base/browser/ui/widget';
import { Sash, IHorizontalSashLayoutProvider, ISashEvent, Orientation } from 'vs/base/browser/ui/sash/sash';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
import { FIND_IDS, MATCHES_LIMIT } from 'vs/editor/contrib/find/common/findModel';
import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/common/findState';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { CONTEXT_FIND_INPUT_FOCUSED } from 'vs/editor/contrib/find/common/findController';
import { ITheme, registerThemingParticipant, IThemeService } from 'vs/platform/theme/common/themeService';
import { Color } from 'vs/base/common/color';
import { editorFindRangeHighlight, editorFindMatch, editorFindMatchHighlight, activeContrastBorder, contrastBorder, inputBackground, editorWidgetBackground, inputActiveOptionBorder, widgetShadow, inputForeground, inputBorder, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationErrorBackground, inputValidationErrorBorder, errorForeground } from 'vs/platform/theme/common/colorRegistry';
import { IEditorAction } from 'vs/editor/common/editorCommon';
import { IDisposable } from 'vs/base/common/lifecycle';
const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find");
const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find");
const NLS_PREVIOUS_MATCH_BTN_LABEL = nls.localize('label.previousMatchButton', "Previous match");
const NLS_NEXT_MATCH_BTN_LABEL = nls.localize('label.nextMatchButton', "Next match");
const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close");
const NLS_MATCHES_COUNT_LIMIT_TITLE = nls.localize('title.matchesCountLimit', "Only the first 999 results are highlighted, but all find operations work on the entire text.");
const NLS_MATCHES_LOCATION = nls.localize('label.matchesLocation', "{0} of {1}");
const NLS_NO_RESULTS = nls.localize('label.noResults', "No Results");
const FIND_WIDGET_INITIAL_WIDTH = 411;
const PART_WIDTH = 275;
const FIND_INPUT_AREA_WIDTH = PART_WIDTH - 54;
let MAX_MATCHES_COUNT_WIDTH = 69;
export const ACTION_IDS = {
FIND_NEXT: 'findNext',
FIND_PREVIOUS: 'findPrev'
};
export interface ITableController {
focus(): void;
getConfiguration(): any;
layoutOverlayWidget(widget: IOverlayWidget): void;
addOverlayWidget(widget: IOverlayWidget): void;
getAction(id: string): IEditorAction;
onDidChangeConfiguration(fn: (e: IConfigurationChangedEvent) => void): IDisposable;
}
export interface IConfigurationChangedEvent {
layoutInfo?: boolean;
}
export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSashLayoutProvider {
private static ID = 'editor.contrib.findWidget';
private _tableController: ITableController;
private _state: FindReplaceState;
private _contextViewProvider: IContextViewProvider;
private _keybindingService: IKeybindingService;
private _domNode: HTMLElement;
private _findInput: FindInput;
private _matchesCount: HTMLElement;
private _prevBtn: SimpleButton;
private _nextBtn: SimpleButton;
private _closeBtn: SimpleButton;
private _isVisible: boolean;
private _focusTracker: dom.IFocusTracker;
private _findInputFocussed: IContextKey<boolean>;
private _resizeSash: Sash;
constructor(
tableController: ITableController,
state: FindReplaceState,
contextViewProvider: IContextViewProvider,
keybindingService: IKeybindingService,
contextKeyService: IContextKeyService,
themeService: IThemeService
) {
super();
this._tableController = tableController;
this._state = state;
this._contextViewProvider = contextViewProvider;
this._keybindingService = keybindingService;
this._isVisible = false;
this._register(this._state.addChangeListener((e) => this._onStateChanged(e)));
this._buildDomNode();
this._updateButtons();
let checkEditorWidth = () => {
let editorWidth = this._tableController.getConfiguration().layoutInfo.width;
let collapsedFindWidget = false;
let reducedFindWidget = false;
let narrowFindWidget = false;
let widgetWidth = dom.getTotalWidth(this._domNode);
if (widgetWidth > FIND_WIDGET_INITIAL_WIDTH) {
// as the widget is resized by users, we may need to change the max width of the widget as the editor width changes.
this._domNode.style.maxWidth = `${editorWidth - 28 - 15}px`;
return;
}
if (FIND_WIDGET_INITIAL_WIDTH + 28 >= editorWidth) {
reducedFindWidget = true;
}
if (FIND_WIDGET_INITIAL_WIDTH + 28 - MAX_MATCHES_COUNT_WIDTH >= editorWidth) {
narrowFindWidget = true;
}
if (FIND_WIDGET_INITIAL_WIDTH + 28 - MAX_MATCHES_COUNT_WIDTH >= editorWidth + 50) {
collapsedFindWidget = true;
}
dom.toggleClass(this._domNode, 'collapsed-find-widget', collapsedFindWidget);
dom.toggleClass(this._domNode, 'narrow-find-widget', narrowFindWidget);
dom.toggleClass(this._domNode, 'reduced-find-widget', reducedFindWidget);
if (!narrowFindWidget && !collapsedFindWidget) {
// the minimal left offset of findwidget is 15px.
this._domNode.style.maxWidth = `${editorWidth - 28 - 15}px`;
}
};
checkEditorWidth();
this._register(this._tableController.onDidChangeConfiguration((e: IConfigurationChangedEvent) => {
if (e.layoutInfo) {
checkEditorWidth();
}
}));
this._findInputFocussed = CONTEXT_FIND_INPUT_FOCUSED.bindTo(contextKeyService);
this._focusTracker = this._register(dom.trackFocus(this._findInput.inputBox.inputElement));
this._focusTracker.addFocusListener(() => {
this._findInputFocussed.set(true);
});
this._focusTracker.addBlurListener(() => {
this._findInputFocussed.set(false);
});
this._tableController.addOverlayWidget(this);
this._applyTheme(themeService.getTheme());
this._register(themeService.onThemeChange(this._applyTheme.bind(this)));
}
// ----- IOverlayWidget API
public getId(): string {
return FindWidget.ID;
}
public getDomNode(): HTMLElement {
return this._domNode;
}
public getPosition(): IOverlayWidgetPosition {
if (this._isVisible) {
return {
preference: OverlayWidgetPositionPreference.TOP_RIGHT_CORNER
};
}
return null;
}
// ----- React to state changes
private _onStateChanged(e: FindReplaceStateChangedEvent): void {
if (e.searchString) {
this._findInput.setValue(this._state.searchString);
this._updateButtons();
}
if (e.isRevealed) {
if (this._state.isRevealed) {
this._reveal(true);
} else {
this._hide(true);
}
}
if (e.isRegex) {
this._findInput.setRegex(this._state.isRegex);
}
if (e.wholeWord) {
this._findInput.setWholeWords(this._state.wholeWord);
}
if (e.matchCase) {
this._findInput.setCaseSensitive(this._state.matchCase);
}
if (e.searchString || e.matchesCount || e.matchesPosition) {
let showRedOutline = (this._state.searchString.length > 0 && this._state.matchesCount === 0);
dom.toggleClass(this._domNode, 'no-results', showRedOutline);
this._updateMatchesCount();
}
}
private _updateMatchesCount(): void {
this._matchesCount.style.minWidth = MAX_MATCHES_COUNT_WIDTH + 'px';
if (this._state.matchesCount >= MATCHES_LIMIT) {
this._matchesCount.title = NLS_MATCHES_COUNT_LIMIT_TITLE;
} else {
this._matchesCount.title = '';
}
// remove previous content
if (this._matchesCount.firstChild) {
this._matchesCount.removeChild(this._matchesCount.firstChild);
}
let label: string;
if (this._state.matchesCount > 0) {
let matchesCount: string = String(this._state.matchesCount);
if (this._state.matchesCount >= MATCHES_LIMIT) {
matchesCount += '+';
}
let matchesPosition: string = String(this._state.matchesPosition);
if (matchesPosition === '0') {
matchesPosition = '?';
}
label = strings.format(NLS_MATCHES_LOCATION, matchesPosition, matchesCount);
} else {
label = NLS_NO_RESULTS;
}
this._matchesCount.appendChild(document.createTextNode(label));
MAX_MATCHES_COUNT_WIDTH = Math.max(MAX_MATCHES_COUNT_WIDTH, this._matchesCount.clientWidth);
}
// ----- actions
private _updateButtons(): void {
this._findInput.setEnabled(this._isVisible);
this._closeBtn.setEnabled(this._isVisible);
let findInputIsNonEmpty = (this._state.searchString.length > 0);
this._prevBtn.setEnabled(this._isVisible && findInputIsNonEmpty);
this._nextBtn.setEnabled(this._isVisible && findInputIsNonEmpty);
}
private _reveal(animate: boolean): void {
if (!this._isVisible) {
this._isVisible = true;
this._updateButtons();
setTimeout(() => {
dom.addClass(this._domNode, 'visible');
this._domNode.setAttribute('aria-hidden', 'false');
if (!animate) {
dom.addClass(this._domNode, 'noanimation');
setTimeout(() => {
dom.removeClass(this._domNode, 'noanimation');
}, 200);
}
}, 0);
this._tableController.layoutOverlayWidget(this);
}
}
private _hide(focusTheEditor: boolean): void {
if (this._isVisible) {
this._isVisible = false;
this._updateButtons();
dom.removeClass(this._domNode, 'visible');
this._domNode.setAttribute('aria-hidden', 'true');
if (focusTheEditor) {
this._tableController.focus();
}
this._tableController.layoutOverlayWidget(this);
}
}
private _applyTheme(theme: ITheme) {
let inputStyles: IFindInputStyles = {
inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder),
inputBackground: theme.getColor(inputBackground),
inputForeground: theme.getColor(inputForeground),
inputBorder: theme.getColor(inputBorder),
inputValidationInfoBackground: theme.getColor(inputValidationInfoBackground),
inputValidationInfoBorder: theme.getColor(inputValidationInfoBorder),
inputValidationWarningBackground: theme.getColor(inputValidationWarningBackground),
inputValidationWarningBorder: theme.getColor(inputValidationWarningBorder),
inputValidationErrorBackground: theme.getColor(inputValidationErrorBackground),
inputValidationErrorBorder: theme.getColor(inputValidationErrorBorder)
};
this._findInput.style(inputStyles);
}
// ----- Public
public focusFindInput(): void {
this._findInput.select();
// Edge browser requires focus() in addition to select()
this._findInput.focus();
}
public highlightFindOptions(): void {
this._findInput.highlightFindOptions();
}
private _onFindInputMouseDown(e: IMouseEvent): void {
// on linux, middle key does pasting.
if (e.middleButton) {
e.stopPropagation();
}
}
private _onFindInputKeyDown(e: IKeyboardEvent): void {
if (e.equals(KeyCode.Enter)) {
this._tableController.getAction(ACTION_IDS.FIND_NEXT).run().done(null, onUnexpectedError);
e.preventDefault();
return;
}
if (e.equals(KeyMod.Shift | KeyCode.Enter)) {
this._tableController.getAction(ACTION_IDS.FIND_NEXT).run().done(null, onUnexpectedError);
e.preventDefault();
return;
}
if (e.equals(KeyCode.Tab)) {
this._findInput.focusOnCaseSensitive();
e.preventDefault();
return;
}
if (e.equals(KeyMod.CtrlCmd | KeyCode.DownArrow)) {
this._tableController.focus();
e.preventDefault();
return;
}
}
// ----- sash
public getHorizontalSashTop(sash: Sash): number {
return 0;
}
public getHorizontalSashLeft?(sash: Sash): number {
return 0;
}
public getHorizontalSashWidth?(sash: Sash): number {
return 500;
}
// ----- initialization
private _keybindingLabelFor(actionId: string): string {
let kb = this._keybindingService.lookupKeybinding(actionId);
if (!kb) {
return '';
}
return ` (${kb.getLabel()})`;
}
private _buildFindPart(): HTMLElement {
// Find input
this._findInput = this._register(new FindInput(null, this._contextViewProvider, {
width: FIND_INPUT_AREA_WIDTH,
label: NLS_FIND_INPUT_LABEL,
placeholder: NLS_FIND_INPUT_PLACEHOLDER,
appendCaseSensitiveLabel: this._keybindingLabelFor(FIND_IDS.ToggleCaseSensitiveCommand),
appendWholeWordsLabel: this._keybindingLabelFor(FIND_IDS.ToggleWholeWordCommand),
appendRegexLabel: this._keybindingLabelFor(FIND_IDS.ToggleRegexCommand),
validation: (value: string): InputBoxMessage => {
if (value.length === 0) {
return null;
}
if (!this._findInput.getRegex()) {
return null;
}
try {
/* tslint:disable:no-unused-expression */
new RegExp(value);
/* tslint:enable:no-unused-expression */
return null;
} catch (e) {
return { content: e.message };
}
}
}));
this._findInput.setRegex(!!this._state.isRegex);
this._findInput.setCaseSensitive(!!this._state.matchCase);
this._findInput.setWholeWords(!!this._state.wholeWord);
this._register(this._findInput.onKeyDown((e) => this._onFindInputKeyDown(e)));
this._register(this._findInput.onInput(() => {
this._state.change({ searchString: this._findInput.getValue() }, true);
}));
this._register(this._findInput.onDidOptionChange(() => {
this._state.change({
isRegex: this._findInput.getRegex(),
wholeWord: this._findInput.getWholeWords(),
matchCase: this._findInput.getCaseSensitive()
}, true);
}));
if (platform.isLinux) {
this._register(this._findInput.onMouseDown((e) => this._onFindInputMouseDown(e)));
}
this._matchesCount = document.createElement('div');
this._matchesCount.className = 'matchesCount';
this._updateMatchesCount();
// Previous button
this._prevBtn = this._register(new SimpleButton({
label: NLS_PREVIOUS_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.PreviousMatchFindAction),
className: 'previous',
onTrigger: () => {
this._tableController.getAction(ACTION_IDS.FIND_PREVIOUS).run().done(null, onUnexpectedError);
},
onKeyDown: (e) => { }
}));
// Next button
this._nextBtn = this._register(new SimpleButton({
label: NLS_NEXT_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.NextMatchFindAction),
className: 'next',
onTrigger: () => {
this._tableController.getAction(ACTION_IDS.FIND_NEXT).run().done(null, onUnexpectedError);
},
onKeyDown: (e) => { }
}));
let findPart = document.createElement('div');
findPart.className = 'find-part';
findPart.appendChild(this._findInput.domNode);
findPart.appendChild(this._matchesCount);
findPart.appendChild(this._prevBtn.domNode);
findPart.appendChild(this._nextBtn.domNode);
// Close button
this._closeBtn = this._register(new SimpleButton({
label: NLS_CLOSE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.CloseFindWidgetCommand),
className: 'close-fw',
onTrigger: () => {
this._state.change({ isRevealed: false, searchScope: null }, false);
},
onKeyDown: () => { }
}));
findPart.appendChild(this._closeBtn.domNode);
return findPart;
}
private _buildDomNode(): void {
// Find part
let findPart = this._buildFindPart();
// Widget
this._domNode = document.createElement('div');
this._domNode.className = 'editor-widget find-widget';
this._domNode.setAttribute('aria-hidden', 'true');
this._domNode.appendChild(findPart);
this._buildSash();
}
private _buildSash() {
this._resizeSash = new Sash(this._domNode, this, { orientation: Orientation.VERTICAL });
let originalWidth = FIND_WIDGET_INITIAL_WIDTH;
this._register(this._resizeSash.addListener('start', (e: ISashEvent) => {
originalWidth = dom.getTotalWidth(this._domNode);
}));
this._register(this._resizeSash.addListener('change', (evt: ISashEvent) => {
let width = originalWidth + evt.startX - evt.currentX;
if (width < FIND_WIDGET_INITIAL_WIDTH) {
// narrow down the find widget should be handled by CSS.
return;
}
let maxWidth = parseFloat(dom.getComputedStyle(this._domNode).maxWidth) || 0;
if (width > maxWidth) {
return;
}
this._domNode.style.width = `${width}px`;
}));
}
}
interface ISimpleCheckboxOpts {
parent: HTMLElement;
title: string;
onChange: () => void;
}
class SimpleCheckbox extends Widget {
private static _COUNTER = 0;
private _opts: ISimpleCheckboxOpts;
private _domNode: HTMLElement;
private _checkbox: HTMLInputElement;
private _label: HTMLLabelElement;
constructor(opts: ISimpleCheckboxOpts) {
super();
this._opts = opts;
this._domNode = document.createElement('div');
this._domNode.className = 'monaco-checkbox';
this._domNode.title = this._opts.title;
this._domNode.tabIndex = 0;
this._checkbox = document.createElement('input');
this._checkbox.type = 'checkbox';
this._checkbox.className = 'checkbox';
this._checkbox.id = 'checkbox-' + SimpleCheckbox._COUNTER++;
this._checkbox.tabIndex = -1;
this._label = document.createElement('label');
this._label.className = 'label';
// Connect the label and the checkbox. Checkbox will get checked when the label recieves a click.
this._label.htmlFor = this._checkbox.id;
this._label.tabIndex = -1;
this._domNode.appendChild(this._checkbox);
this._domNode.appendChild(this._label);
this._opts.parent.appendChild(this._domNode);
this.onchange(this._checkbox, (e) => {
this._opts.onChange();
});
}
public get domNode(): HTMLElement {
return this._domNode;
}
public get checked(): boolean {
return this._checkbox.checked;
}
public set checked(newValue: boolean) {
this._checkbox.checked = newValue;
}
public focus(): void {
this._checkbox.focus();
}
private enable(): void {
this._checkbox.removeAttribute('disabled');
}
private disable(): void {
this._checkbox.disabled = true;
}
public setEnabled(enabled: boolean): void {
if (enabled) {
this.enable();
this.domNode.tabIndex = 0;
} else {
this.disable();
this.domNode.tabIndex = -1;
}
}
}
interface ISimpleButtonOpts {
label: string;
className: string;
onTrigger: () => void;
onKeyDown: (e: IKeyboardEvent) => void;
}
class SimpleButton extends Widget {
private _opts: ISimpleButtonOpts;
private _domNode: HTMLElement;
constructor(opts: ISimpleButtonOpts) {
super();
this._opts = opts;
this._domNode = document.createElement('div');
this._domNode.title = this._opts.label;
this._domNode.tabIndex = 0;
this._domNode.className = 'button ' + this._opts.className;
this._domNode.setAttribute('role', 'button');
this._domNode.setAttribute('aria-label', this._opts.label);
this.onclick(this._domNode, (e) => {
this._opts.onTrigger();
e.preventDefault();
});
this.onkeydown(this._domNode, (e) => {
if (e.equals(KeyCode.Space) || e.equals(KeyCode.Enter)) {
this._opts.onTrigger();
e.preventDefault();
return;
}
this._opts.onKeyDown(e);
});
}
public get domNode(): HTMLElement {
return this._domNode;
}
public isEnabled(): boolean {
return (this._domNode.tabIndex >= 0);
}
public focus(): void {
this._domNode.focus();
}
public setEnabled(enabled: boolean): void {
dom.toggleClass(this._domNode, 'disabled', !enabled);
this._domNode.setAttribute('aria-disabled', String(!enabled));
this._domNode.tabIndex = enabled ? 0 : -1;
}
public setExpanded(expanded: boolean): void {
this._domNode.setAttribute('aria-expanded', String(!!expanded));
}
public toggleClass(className: string, shouldHaveIt: boolean): void {
dom.toggleClass(this._domNode, className, shouldHaveIt);
}
}

View File

@@ -0,0 +1,217 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IProfilerController } from './interfaces';
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
import { Table } from 'sql/base/browser/ui/table/table';
import { attachTableStyler } from 'sql/common/theme/styler';
import { RowSelectionModel } from 'sql/base/browser/ui/table/plugins/rowSelectionModel.plugin';
import { IProfilerStateChangedEvent } from 'sql/parts/profiler/editor/profilerState';
import { FindWidget, ITableController, IConfigurationChangedEvent, ACTION_IDS } from './profilerFindWidget';
import { ProfilerFindNext, ProfilerFindPrevious } from 'sql/parts/profiler/contrib/profilerActions';
import { TPromise } from 'vs/base/common/winjs.base';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IEditorAction } from 'vs/editor/common/editorCommon';
import { IOverlayWidget } from 'vs/editor/browser/editorBrowser';
import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/common/findState';
import { Dimension, Builder } from 'vs/base/browser/builder';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import Event, { Emitter } from 'vs/base/common/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export class ProfilerTableEditor extends BaseEditor implements IProfilerController, ITableController {
public static ID: string = 'workbench.editor.profiler.table';
protected _input: ProfilerInput;
private _profilerTable: Table<Slick.SlickData>;
private _columnListener: IDisposable;
private _stateListener: IDisposable;
private _findCountChangeListener: IDisposable;
private _findState: FindReplaceState;
private _finder: FindWidget;
private _overlay: HTMLElement;
private _currentDimensions: Dimension;
private _actionMap: { [x: string]: IEditorAction } = {};
private _onDidChangeConfiguration = new Emitter<IConfigurationChangedEvent>();
public onDidChangeConfiguration: Event<IConfigurationChangedEvent> = this._onDidChangeConfiguration.event;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IWorkbenchThemeService private _themeService: IWorkbenchThemeService,
@IContextViewService private _contextViewService: IContextViewService,
@IKeybindingService private _keybindingService: IKeybindingService,
@IContextKeyService private _contextKeyService: IContextKeyService,
@IInstantiationService private _instantiationService: IInstantiationService
) {
super(ProfilerTableEditor.ID, telemetryService, _themeService);
this._actionMap[ACTION_IDS.FIND_NEXT] = this._instantiationService.createInstance(ProfilerFindNext, this);
this._actionMap[ACTION_IDS.FIND_PREVIOUS] = this._instantiationService.createInstance(ProfilerFindPrevious, this);
}
public createEditor(parent: Builder): void {
this._overlay = document.createElement('div');
this._overlay.className = 'overlayWidgets';
this._overlay.style.width = '100%';
this._overlay.style.zIndex = '4';
parent.getHTMLElement().appendChild(this._overlay);
this._profilerTable = new Table(parent.getHTMLElement());
this._profilerTable.setSelectionModel(new RowSelectionModel());
attachTableStyler(this._profilerTable, this._themeService);
this._findState = new FindReplaceState();
this._findState.addChangeListener(e => this._onFindStateChange(e));
this._finder = new FindWidget(
this,
this._findState,
this._contextViewService,
this._keybindingService,
this._contextKeyService,
this._themeService
);
}
public setInput(input: ProfilerInput): TPromise<void> {
this._input = input;
if (this._columnListener) {
this._columnListener.dispose();
}
this._columnListener = input.onColumnsChanged(e => {
this._profilerTable.columns = e;
this._profilerTable.autosizeColumns();
});
if (this._stateListener) {
this._stateListener.dispose();
}
this._stateListener = input.state.addChangeListener(e => this._onStateChange(e));
if (this._findCountChangeListener) {
this._findCountChangeListener.dispose();
}
this._findCountChangeListener = input.data.onFindCountChange(() => this._updateFinderMatchState());
this._profilerTable.setData(input.data);
this._profilerTable.columns = input.columns;
this._profilerTable.autosizeColumns();
this._input.data.currentFindPosition.then(val => {
this._profilerTable.setActiveCell(val.row, val.col);
this._updateFinderMatchState();
}, er => { });
return TPromise.as(null);
}
public toggleSearch(): void {
this._findState.change({
isRevealed: true
}, false);
this._finder.focusFindInput();
}
public findNext(): void {
this._input.data.findNext().then(p => {
this._profilerTable.setActiveCell(p.row, p.col);
this._updateFinderMatchState();
}, er => { });
}
public findPrevious(): void {
this._input.data.findPrevious().then(p => {
this._profilerTable.setActiveCell(p.row, p.col);
this._updateFinderMatchState();
}, er => { });
}
public getConfiguration() {
return {
layoutInfo: {
width: this._currentDimensions ? this._currentDimensions.width : 0
}
};
}
public layoutOverlayWidget(widget: IOverlayWidget): void {
// no op
}
public addOverlayWidget(widget: IOverlayWidget): void {
let domNode = widget.getDomNode();
domNode.style.right = '28px';
this._overlay.appendChild(widget.getDomNode());
this._findState.change({ isRevealed: false }, false);
}
public getAction(id: string): IEditorAction {
return this._actionMap[id];
}
public focus(): void {
this._profilerTable.focus();
}
public layout(dimension: Dimension): void {
this._currentDimensions = dimension;
this._profilerTable.layout(dimension);
this._onDidChangeConfiguration.fire({ layoutInfo: true });
}
public onSelectedRowsChanged(fn: (e: Slick.EventData, args: Slick.OnSelectedRowsChangedEventArgs<Slick.SlickData>) => any): void {
if (this._profilerTable) {
this._profilerTable.onSelectedRowsChanged(fn);
}
}
private _onStateChange(e: IProfilerStateChangedEvent): void {
if (e.autoscroll) {
this._profilerTable.autoScroll = this._input.state.autoscroll;
}
}
public updateState(): void {
this._onStateChange({ autoscroll: true });
}
private _onFindStateChange(e: FindReplaceStateChangedEvent): void {
if (e.isRevealed) {
if (this._findState.isRevealed) {
this._finder.getDomNode().style.top = '0px';
this._updateFinderMatchState();
} else {
this._finder.getDomNode().style.top = '';
}
}
if (e.searchString) {
if (this._input && this._input.data) {
if (this._findState.searchString) {
this._input.data.find(this._findState.searchString).then(p => {
if (p) {
this._profilerTable.setActiveCell(p.row, p.col);
this._updateFinderMatchState();
}
});
} else {
this._input.data.clearFind();
}
}
}
}
private _updateFinderMatchState(): void {
if (this._input && this._input.data) {
this._findState.changeMatchInfo(this._input.data.findPosition, this._input.data.findCount, undefined);
} else {
this._findState.changeMatchInfo(0, 0, undefined);
}
}
}

View File

@@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
export const CONTEXT_PROFILER_EDITOR = new RawContextKey<boolean>('inProfilerTableEditor', false);
export const PROFILER_TABLE_COMMAND_SEARCH = 'profiler.table.action.search';

View File

@@ -0,0 +1,452 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ProfilerInput } from './profilerInput';
import { TabbedPanel } from 'sql/base/browser/ui/panel/panel';
import { Table } from 'sql/base/browser/ui/table/table';
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
import { IProfilerService, IProfilerSessionTemplate } from 'sql/parts/profiler/service/interfaces';
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
import { attachTableStyler } from 'sql/common/theme/styler';
import { IProfilerStateChangedEvent } from './profilerState';
import { ProfilerTableEditor } from './controller/profilerTableEditor';
import * as Actions from 'sql/parts/profiler/contrib/profilerActions';
import { CONTEXT_PROFILER_EDITOR, PROFILER_TABLE_COMMAND_SEARCH } from './interfaces';
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
import * as DOM from 'vs/base/browser/dom';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { Dimension, Builder } from 'vs/base/browser/builder';
import { TPromise } from 'vs/base/common/winjs.base';
import { EditorOptions } from 'vs/workbench/common/editor';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ProfilerResourceEditor } from './profilerResourceEditor';
import { SplitView, View, Orientation, IViewOptions } from 'vs/base/browser/ui/splitview/splitview';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IModel, ICommonCodeEditor } from 'vs/editor/common/editorCommon';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import URI from 'vs/base/common/uri';
import { UNTITLED_SCHEMA } from 'vs/workbench/services/untitled/common/untitledEditorService';
import * as nls from 'vs/nls';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Command } from 'vs/editor/common/editorCommonExtensions';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { ContextKeyExpr, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { CommonFindController, FindStartFocusAction } from 'vs/editor/contrib/find/common/findController';
import * as types from 'vs/base/common/types';
import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
import { ProfilerTestBackend } from 'sql/parts/profiler/service/profilerTestBackend';
class BasicView extends View {
private _previousSize: number;
private _collapsed: boolean;
public headerSize: number;
constructor(
initialSize: number,
private _element: HTMLElement,
private _focus: () => void,
private _layout: (size: number, orientation: Orientation) => void,
opts: IViewOptions
) {
super(initialSize, opts);
}
render(container: HTMLElement, orientation: Orientation): void {
container.appendChild(this._element);
}
focus(): void {
this._focus();
}
layout(size: number, orientation: Orientation): void {
if (!this.collapsed) {
this._previousSize = size;
}
this._layout(size, orientation);
}
set collapsed(val: boolean) {
this._collapsed = val === false ? false : true;
if (this.collapsed) {
this._previousSize = this.size;
this.setFixed(this.headerSize);
} else {
this.setFlexible(this._previousSize);
}
}
get collapsed(): boolean {
return this._collapsed;
}
}
export interface IDetailData {
label: string;
value: string;
}
export class ProfilerEditor extends BaseEditor {
public static ID: string = 'workbench.editor.profiler';
private _editor: ProfilerResourceEditor;
private _editorModel: IModel;
private _editorInput: UntitledEditorInput;
private _splitView: SplitView;
private _container: HTMLElement;
private _body: HTMLElement;
private _header: HTMLElement;
private _actionBar: Taskbar;
private _tabbedPanel: TabbedPanel;
private _profilerTableEditor: ProfilerTableEditor;
private _detailTable: Table<IDetailData>;
private _detailTableData: TableDataView<IDetailData>;
private _stateListener: IDisposable;
private _panelView: BasicView;
private _profilerEditorContextKey: IContextKey<boolean>;
private _sessionTemplateSelector: SelectBox;
private _sessionTemplates: Array<IProfilerSessionTemplate>;
// Actions
private _connectAction: Actions.ProfilerConnect;
private _startAction: Actions.ProfilerStart;
private _pauseAction: Actions.ProfilerPause;
private _stopAction: Actions.ProfilerStop;
private _autoscrollAction: Actions.ProfilerAutoScroll;
private _collapsedPanelAction: Actions.ProfilerCollapsablePanelAction;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IWorkbenchThemeService themeService: IWorkbenchThemeService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IContextMenuService private _contextMenuService: IContextMenuService,
@IModelService private _modelService: IModelService,
@IProfilerService private _profilerService: IProfilerService,
@IContextKeyService private _contextKeyService: IContextKeyService,
@IContextViewService private _contextViewService: IContextViewService
) {
super(ProfilerEditor.ID, telemetryService, themeService);
this._profilerEditorContextKey = CONTEXT_PROFILER_EDITOR.bindTo(this._contextKeyService);
}
protected createEditor(parent: Builder): void {
// test backend
this._profilerService.registerProvider('default', this._instantiationService.createInstance(ProfilerTestBackend));
this._container = document.createElement('div');
this._container.className = 'carbon-profiler';
parent.append(this._container);
this._createHeader();
this._body = document.createElement('div');
this._body.className = 'profiler-body';
this._container.appendChild(this._body);
this._splitView = new SplitView(this._body);
let tableContainer = this._createProfilerTable();
let paneContainer = this._createProfilerPane();
this._splitView.addView(new BasicView(
undefined,
tableContainer,
() => this._profilerTableEditor.focus(),
size => this._profilerTableEditor.layout(new Dimension(parseFloat(DOM.getComputedStyle(this._body).width), size)),
{}
));
this._panelView = new BasicView(
undefined,
paneContainer,
() => this._tabbedPanel.focus(),
size => this._tabbedPanel.layout(new Dimension(DOM.getTotalWidth(this._body), size)),
{ minimumSize: 35 }
);
this._panelView.headerSize = 35;
this._splitView.addView(this._panelView);
}
private _createHeader(): void {
this._header = document.createElement('div');
this._header.className = 'profiler-header';
this._container.appendChild(this._header);
this._actionBar = new Taskbar(this._header, this._contextMenuService);
this._startAction = this._instantiationService.createInstance(Actions.ProfilerStart, Actions.ProfilerStart.ID, Actions.ProfilerStart.LABEL);
this._startAction.enabled = false;
this._stopAction = this._instantiationService.createInstance(Actions.ProfilerStop, Actions.ProfilerStop.ID, Actions.ProfilerStop.LABEL);
this._stopAction.enabled = false;
this._pauseAction = this._instantiationService.createInstance(Actions.ProfilerPause, Actions.ProfilerPause.ID, Actions.ProfilerPause.LABEL);
this._pauseAction.enabled = false;
this._connectAction = this._instantiationService.createInstance(Actions.ProfilerConnect, Actions.ProfilerConnect.ID, Actions.ProfilerConnect.LABEL);
this._autoscrollAction = this._instantiationService.createInstance(Actions.ProfilerAutoScroll, Actions.ProfilerAutoScroll.ID, Actions.ProfilerAutoScroll.LABEL);
this._sessionTemplates = this._profilerService.getSessionTemplates();
this._sessionTemplateSelector = new SelectBox(this._sessionTemplates.map(i => i.name), 'Standard');
this._register(this._sessionTemplateSelector.onDidSelect(e => {
if (this.input) {
this.input.sessionTemplate = this._sessionTemplates.find(i => i.name === e.selected);
}
}));
let dropdownContainer = document.createElement('div');
dropdownContainer.style.width = '150px';
this._sessionTemplateSelector.render(dropdownContainer);
this._register(attachSelectBoxStyler(this._sessionTemplateSelector, this.themeService));
this._actionBar.setContent([
{ action: this._startAction },
{ action: this._pauseAction },
{ action: this._stopAction },
{ action: this._connectAction },
{ element: Taskbar.createTaskbarSeparator() },
{ action: this._autoscrollAction },
{ action: this._instantiationService.createInstance(Actions.ProfilerClear, Actions.ProfilerClear.ID, Actions.ProfilerClear.LABEL) },
{ element: dropdownContainer },
{ action: this._instantiationService.createInstance(Actions.ProfilerEditColumns, Actions.ProfilerEditColumns.ID, Actions.ProfilerEditColumns.LABEL) }
]);
}
private _createProfilerTable(): HTMLElement {
let profilerTableContainer = document.createElement('div');
profilerTableContainer.className = 'profiler-table monaco-editor';
profilerTableContainer.style.width = '100%';
profilerTableContainer.style.height = '100%';
profilerTableContainer.style.overflow = 'hidden';
profilerTableContainer.style.position = 'relative';
this._profilerTableEditor = this._instantiationService.createInstance(ProfilerTableEditor);
this._profilerTableEditor.createEditor(new Builder(profilerTableContainer));
this._profilerTableEditor.onSelectedRowsChanged((e, args) => {
let data = this.input.data.getItem(args.rows[0]);
if (data) {
this._modelService.updateModel(this._editorModel, data['TextData']);
this._detailTableData.clear();
this._detailTableData.push(Object.keys(data).map(key => {
return {
label: key,
value: data[key]
};
}));
if (this.input && types.isUndefinedOrNull(this.input.state.isPanelCollapsed)) {
this.input.state.change({ isPanelCollapsed: false });
}
} else {
this._modelService.updateModel(this._editorModel, '');
this._detailTableData.clear();
}
});
return profilerTableContainer;
}
private _createProfilerPane(): HTMLElement {
let editorContainer = this._createProfilerEditor();
let tabbedPanelContainer = document.createElement('div');
tabbedPanelContainer.className = 'profiler-tabbedPane';
this._tabbedPanel = new TabbedPanel(tabbedPanelContainer);
this._tabbedPanel.pushTab({
identifier: 'editor',
title: nls.localize('text', "Text"),
view: {
layout: dim => this._editor.layout(dim),
render: parent => parent.appendChild(editorContainer)
}
});
let detailTableContainer = document.createElement('div');
detailTableContainer.className = 'profiler-detailTable';
detailTableContainer.style.width = '100%';
detailTableContainer.style.height = '100%';
this._detailTableData = new TableDataView<IDetailData>();
this._detailTable = new Table(detailTableContainer, this._detailTableData, [
{
id: 'label',
name: nls.localize('label', "Label"),
field: 'label'
},
{
id: 'value',
name: nls.localize('value', "Value"),
field: 'value'
}
], { forceFitColumns: true });
this._tabbedPanel.pushTab({
identifier: 'detailTable',
title: nls.localize('details', "Details"),
view: {
layout: dim => this._detailTable.layout(dim),
render: parent => parent.appendChild(detailTableContainer)
}
});
this._collapsedPanelAction = this._instantiationService.createInstance(Actions.ProfilerCollapsablePanelAction, Actions.ProfilerCollapsablePanelAction.ID, Actions.ProfilerCollapsablePanelAction.LABEL);
this._tabbedPanel.pushAction(this._collapsedPanelAction, { icon: true, label: false });
this._register(attachTableStyler(this._detailTable, this.themeService));
return tabbedPanelContainer;
}
private _createProfilerEditor(): HTMLElement {
this._editor = this._instantiationService.createInstance(ProfilerResourceEditor);
let editorContainer = document.createElement('div');
editorContainer.className = 'profiler-editor';
this._editor.create(new Builder(editorContainer));
this._editor.setVisible(true);
this._editorInput = this._instantiationService.createInstance(UntitledEditorInput, URI.from({ scheme: UNTITLED_SCHEMA }), false, 'sql', '', '');
this._editor.setInput(this._editorInput, undefined);
this._editorInput.resolve().then(model => this._editorModel = model.textEditorModel);
return editorContainer;
}
public get input(): ProfilerInput {
return this._input as ProfilerInput;
}
public setInput(input: ProfilerInput, options?: EditorOptions): TPromise<void> {
this._profilerEditorContextKey.set(true);
if (input instanceof ProfilerInput && input.matches(this.input)) {
return TPromise.as(null);
}
return super.setInput(input, options).then(() => {
this._profilerTableEditor.setInput(input);
if (input.sessionTemplate) {
this._sessionTemplateSelector.selectWithOptionName(input.sessionTemplate.name);
} else {
input.sessionTemplate = this._sessionTemplates.find(i => i.name === 'Standard');
}
this._actionBar.context = input;
this._tabbedPanel.actionBarContext = input;
if (this._stateListener) {
this._stateListener.dispose();
}
this._stateListener = input.state.addChangeListener(e => this._onStateChange(e));
this._onStateChange({
isConnected: true,
isRunning: true,
isPaused: true,
isStopped: true,
autoscroll: true,
isPanelCollapsed: true
});
this._profilerTableEditor.updateState();
this._splitView.layout();
this._profilerTableEditor.focus();
});
}
public clearInput(): void {
this._profilerEditorContextKey.set(false);
}
public toggleSearch(): void {
if (this._editor.getControl().isFocused()) {
let editor = this._editor.getControl() as ICommonCodeEditor;
let controller = CommonFindController.get(editor);
if (controller) {
controller.start({
forceRevealReplace: false,
seedSearchStringFromSelection: (controller.getState().searchString.length === 0),
shouldFocus: FindStartFocusAction.FocusFindInput,
shouldAnimate: true
});
}
} else {
this._profilerTableEditor.toggleSearch();
}
}
private _onStateChange(e: IProfilerStateChangedEvent): void {
if (e.autoscroll) {
this._autoscrollAction.checked = this.input.state.autoscroll;
}
if (e.isPanelCollapsed) {
this._collapsedPanelAction.collapsed = this.input.state.isPanelCollapsed;
this._tabbedPanel.collapsed = this.input.state.isPanelCollapsed;
this._panelView.collapsed = this.input.state.isPanelCollapsed;
}
if (e.isConnected) {
this._connectAction.connected = this.input.state.isConnected;
this._startAction.enabled = this.input.state.isConnected;
this._stopAction.enabled = false;
this._pauseAction.enabled = false;
if (this.input.state.isConnected) {
this._sessionTemplateSelector.disable();
} else {
this._sessionTemplateSelector.enable();
}
return;
}
if (e.isRunning) {
this._startAction.enabled = !this.input.state.isRunning;
}
if (e.isStopped || e.isRunning) {
this._stopAction.enabled = !this.input.state.isStopped && this.input.state.isRunning;
}
if (e.isPaused || e.isRunning) {
this._pauseAction.enabled = !this.input.state.isPaused && this.input.state.isRunning;
}
}
public layout(dimension: Dimension): void {
this._container.style.width = dimension.width + 'px';
this._container.style.height = dimension.height + 'px';
this._body.style.width = dimension.width + 'px';
this._body.style.height = (dimension.height - (28 + 4)) + 'px';
this._splitView.layout(dimension.height - (28 + 4));
}
}
abstract class SettingsCommand extends Command {
protected getProfilerEditor(accessor: ServicesAccessor): ProfilerEditor {
const activeEditor = accessor.get(IWorkbenchEditorService).getActiveEditor();
if (activeEditor instanceof ProfilerEditor) {
return activeEditor;
}
return null;
}
}
class StartSearchProfilerTableCommand extends SettingsCommand {
public runCommand(accessor: ServicesAccessor, args: any): void {
const preferencesEditor = this.getProfilerEditor(accessor);
if (preferencesEditor) {
preferencesEditor.toggleSearch();
}
}
}
const command = new StartSearchProfilerTableCommand({
id: PROFILER_TABLE_COMMAND_SEARCH,
precondition: ContextKeyExpr.and(CONTEXT_PROFILER_EDITOR),
kbOpts: { primary: KeyMod.CtrlCmd | KeyCode.KEY_F }
});
KeybindingsRegistry.registerCommandAndKeybindingRule(command.toCommandAndKeybindingRule(KeybindingsRegistry.WEIGHT.editorContrib()));

View File

@@ -0,0 +1,136 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
import { IProfilerSession, IProfilerService, ProfilerSessionID, IProfilerSessionTemplate } from 'sql/parts/profiler/service/interfaces';
import { ProfilerState } from './profilerState';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import * as data from 'data';
import { TPromise } from 'vs/base/common/winjs.base';
import { EditorInput } from 'vs/workbench/common/editor';
import { IEditorModel } from 'vs/platform/editor/common/editor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import Event, { Emitter } from 'vs/base/common/event';
import { generateUuid } from 'vs/base/common/uuid';
import * as nls from 'vs/nls';
export class ProfilerInput extends EditorInput implements IProfilerSession {
public static ID: string = 'workbench.editorinputs.profilerinputs';
public static SCHEMA: string = 'profiler';
private _data: TableDataView<Slick.SlickData>;
private _id: ProfilerSessionID;
private _state: ProfilerState;
private _columns: string[] = [];
private _sessionTemplate: IProfilerSessionTemplate;
private _onColumnsChanged = new Emitter<Slick.Column<Slick.SlickData>[]>();
public onColumnsChanged: Event<Slick.Column<Slick.SlickData>[]> = this._onColumnsChanged.event;
constructor(
private _connection: IConnectionProfile,
@IInstantiationService private _instantiationService: IInstantiationService,
@IProfilerService private _profilerService: IProfilerService
) {
super();
this._state = new ProfilerState();
// set inital state
this.state.change({
isConnected: false,
isStopped: false,
isPaused: false,
isRunning: false,
autoscroll: true
});
this._id = this._profilerService.registerSession(generateUuid(), this);
let searchFn = (val: { [x: string]: string }, exp: string): Array<number> => {
let ret = new Array<number>();
for (let i = 0; i < this._columns.length; i++) {
if (val[this._columns[i]].includes(exp)) {
ret.push(i);
}
}
return ret;
};
this._data = new TableDataView<Slick.SlickData>(undefined, searchFn);
}
public set sessionTemplate(template: IProfilerSessionTemplate) {
if (!this.state.isConnected || this.state.isStopped) {
this._sessionTemplate = template;
let newColumns = this.sessionTemplate.view.events.reduce<Array<string>>((p, e) => {
e.columns.forEach(c => {
if (!p.includes(c)) {
p.push(c);
}
});
return p;
}, []);
newColumns.unshift('EventClass');
this.setColumns(newColumns);
}
}
public get sessionTemplate(): IProfilerSessionTemplate {
return this._sessionTemplate;
}
public getTypeId(): string {
return ProfilerInput.ID;
}
public resolve(refresh?: boolean): TPromise<IEditorModel> {
return undefined;
}
public getName(): string {
return nls.localize('profiler', 'Profiler');
}
public get data(): TableDataView<Slick.SlickData> {
return this._data;
}
public get columns(): Slick.Column<Slick.SlickData>[] {
if (this._columns) {
return this._columns.map(i => {
return <Slick.Column<Slick.SlickData>>{
id: i,
field: i,
name: i,
sortable: true
};
});
} else {
return [];
}
}
public setColumns(columns: Array<string>) {
this._columns = columns;
this._onColumnsChanged.fire(this.columns);
}
public get id(): ProfilerSessionID {
return this._id;
}
public get state(): ProfilerState {
return this._state;
}
public onMoreRows(rowCount: number, data: data.IProfilerTableRow) {
let validColumns = this.sessionTemplate.view.events.find(i => i.name === data.EventClass).columns;
Object.keys(rowCount).forEach(k => {
if (!validColumns.includes(k)) {
delete rowCount[k];
}
});
this._data.push(data);
}
}

View File

@@ -0,0 +1,96 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import * as nls from 'vs/nls';
import { Dimension, Builder } from 'vs/base/browser/builder';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { TPromise } from 'vs/base/common/winjs.base';
import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
import { IModeService } from 'vs/editor/common/services/modeService';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { EditorOptions } from 'vs/workbench/common/editor';
import { CodeEditor } from 'vs/editor/browser/codeEditor';
import { IEditorContributionCtor } from 'vs/editor/browser/editorBrowser';
import { FoldingController } from 'vs/editor/contrib/folding/browser/folding';
class ProfilerResourceCodeEditor extends CodeEditor {
protected _getContributions(): IEditorContributionCtor[] {
let contributions = super._getContributions();
let skipContributions = [FoldingController.prototype];
contributions = contributions.filter(c => skipContributions.indexOf(c.prototype) === -1);
return contributions;
}
}
/**
* Extension of TextResourceEditor that is always readonly rather than only with non UntitledInputs
*/
export class ProfilerResourceEditor extends BaseTextEditor {
public static ID = 'profiler.editors.textEditor';
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IInstantiationService instantiationService: IInstantiationService,
@IStorageService storageService: IStorageService,
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
@IThemeService themeService: IThemeService,
@IModeService modeService: IModeService,
@ITextFileService textFileService: ITextFileService,
@IEditorGroupService editorGroupService: IEditorGroupService
) {
super(ProfilerResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, modeService, textFileService, editorGroupService);
}
public createEditorControl(parent: Builder, configuration: IEditorOptions): editorCommon.IEditor {
return this.instantiationService.createInstance(ProfilerResourceCodeEditor, parent.getHTMLElement(), configuration);
}
protected getConfigurationOverrides(): IEditorOptions {
const options = super.getConfigurationOverrides();
options.readOnly = true;
if (this.input) {
options.inDiffEditor = true;
options.scrollBeyondLastLine = false;
options.folding = false;
options.renderWhitespace = 'none';
options.wordWrap = 'on';
options.renderIndentGuides = false;
options.rulers = [];
options.glyphMargin = true;
options.minimap = {
enabled: false
};
}
return options;
}
setInput(input: UntitledEditorInput, options: EditorOptions): TPromise<void> {
return super.setInput(input, options)
.then(() => this.input.resolve()
.then(editorModel => editorModel.load())
.then(editorModel => this.getControl().setModel((<ResourceEditorModel>editorModel).textEditorModel)));
}
protected getAriaLabel(): string {
return nls.localize('profilerTextEditorAriaLabel', 'Profiler editor for event text. Readonly');
}
public layout(dimension: Dimension) {
this.getControl().layout(dimension);
}
}

View File

@@ -0,0 +1,116 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { EventEmitter } from 'vs/base/common/eventEmitter';
import { IDisposable } from 'vs/base/common/lifecycle';
export interface IProfilerStateChangedEvent {
isConnected?: boolean;
isRunning?: boolean;
isPaused?: boolean;
isStopped?: boolean;
autoscroll?: boolean;
isPanelCollapsed?: boolean;
}
export interface INewProfilerState {
isConnected?: boolean;
isRunning?: boolean;
isPaused?: boolean;
isStopped?: boolean;
autoscroll?: boolean;
isPanelCollapsed?: boolean;
}
export class ProfilerState implements IDisposable {
private static _CHANGED_EVENT = 'changed';
private _isConnected: boolean;
private _isRunning: boolean;
private _isPaused: boolean;
private _isStopped: boolean;
private _autoscroll: boolean;
private _isPanelCollapsed: boolean;
private _eventEmitter: EventEmitter;
public get isConnected(): boolean { return this._isConnected; }
public get isRunning(): boolean { return this._isRunning; }
public get isPaused(): boolean { return this._isPaused; }
public get isStopped(): boolean { return this._isStopped; }
public get autoscroll(): boolean { return this._autoscroll; }
public get isPanelCollapsed(): boolean { return this._isPanelCollapsed; }
constructor() {
this._eventEmitter = new EventEmitter();
}
public dispose(): void {
this._eventEmitter.dispose();
}
public addChangeListener(listener: (e: IProfilerStateChangedEvent) => void): IDisposable {
return this._eventEmitter.addListener(ProfilerState._CHANGED_EVENT, listener);
}
public change(newState: INewProfilerState): void {
let changeEvent: IProfilerStateChangedEvent = {
isConnected: false,
isRunning: false,
isPaused: false,
isStopped: false,
autoscroll: false,
isPanelCollapsed: false
};
let somethingChanged = false;
if (typeof newState.isConnected !== 'undefined') {
if (this._isConnected !== newState.isConnected) {
this._isConnected = newState.isConnected;
changeEvent.isConnected = true;
somethingChanged = true;
}
}
if (typeof newState.isRunning !== 'undefined') {
if (this._isRunning !== newState.isRunning) {
this._isRunning = newState.isRunning;
changeEvent.isRunning = true;
somethingChanged = true;
}
}
if (typeof newState.isPaused !== 'undefined') {
if (this._isPaused !== newState.isPaused) {
this._isPaused = newState.isPaused;
changeEvent.isPaused = true;
somethingChanged = true;
}
}
if (typeof newState.isStopped !== 'undefined') {
if (this._isStopped !== newState.isStopped) {
this._isStopped = newState.isStopped;
changeEvent.isStopped = true;
somethingChanged = true;
}
}
if (typeof newState.autoscroll !== 'undefined') {
if (this._autoscroll !== newState.autoscroll) {
this._autoscroll = newState.autoscroll;
changeEvent.autoscroll = true;
somethingChanged = true;
}
}
if (typeof newState.isPanelCollapsed !== 'undefined') {
if (this._isPanelCollapsed !== newState.isPanelCollapsed) {
this._isPanelCollapsed = newState.isPanelCollapsed;
changeEvent.isPanelCollapsed = true;
somethingChanged = true;
}
}
if (somethingChanged) {
this._eventEmitter.emit(ProfilerState._CHANGED_EVENT, changeEvent);
}
}
}

View File

@@ -0,0 +1,103 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import * as data from 'data';
const PROFILER_SERVICE_ID = 'profilerService';
export const IProfilerService = createDecorator<IProfilerService>(PROFILER_SERVICE_ID);
export type ProfilerSessionID = string;
export const PROFILER_SESSION_TEMPLATE_SETTINGS = 'profiler.sessionTemplates';
export const PROFILER_SETTINGS = 'profiler';
/**
* A front end provider for a profiler session
*/
export interface IProfilerSession {
/**
* Called by the service when more rows are available to render
*/
onMoreRows(rowCount: number, data: data.IProfilerTableRow);
}
/**
* A Profiler Service that handles session communication between the backends and frontends
*/
export interface IProfilerService {
_serviceBrand: any;
/**
* Registers a backend provider for profiler session. ex: mssql
*/
registerProvider(providerId: string, provider: data.IProfilerProvider): void;
/**
* Registers a session with the service that acts as the UI for a profiler session
* @returns An unique id that should be used to make subsequent calls to this service
*/
registerSession(uri: string, session: IProfilerSession): ProfilerSessionID;
/**
* Connects the session specified by the id
*/
connectSession(sessionId: ProfilerSessionID): Thenable<boolean>;
/**
* Disconnected the session specified by the id
*/
disconnectSession(sessionId: ProfilerSessionID): Thenable<boolean>;
/**
* Starts the session specified by the id
*/
startSession(sessionId: ProfilerSessionID): Thenable<boolean>;
/**
* Pauses the session specified by the id
*/
pauseSession(sessionId: ProfilerSessionID): Thenable<boolean>;
/**
* Stops the session specified by the id
*/
stopSession(sessionId: ProfilerSessionID): Thenable<boolean>;
/**
* The method called by the service provider for when more rows are available to render
*/
onMoreRows(params: data.IProfilerMoreRowsNotificationParams): void;
/**
* Gets a list of the session templates that are specified in the settings
* @param provider An optional string to limit the session template to a specific
* @returns An array of session templates that match the provider passed, if passed, and generic ones (no provider specified),
* otherwise returns all session templates
*/
getSessionTemplates(providerId?: string): Array<IProfilerSessionTemplate>;
/**
* Launches the dialog for editing the view columns of a profiler session template for the given input
* @param input input object that contains the necessary information which will be modified based on used input
*/
launchColumnEditor(input: ProfilerInput): Thenable<void>;
}
export interface IProfilerSettings {
sessionTemplates: Array<IProfilerSessionTemplate>;
}
export interface IEventTemplate {
name: string;
optionalColumns: Array<string>;
}
export interface IEventViewTemplate {
name: string;
columns: Array<string>;
}
export interface ISessionViewTemplate {
events: Array<IEventViewTemplate>;
}
export interface IProfilerSessionTemplate {
name: string;
events: Array<IEventTemplate>;
view: ISessionViewTemplate;
}

View File

@@ -0,0 +1,125 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import {
ProfilerSessionID, IProfilerSession, IProfilerService, IProfilerSessionTemplate,
PROFILER_SETTINGS, IProfilerSettings
} from './interfaces';
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
import { ProfilerColumnEditorDialog } from 'sql/parts/profiler/dialog/profilerColumnEditorDialog';
import * as data from 'data';
import { TPromise } from 'vs/base/common/winjs.base';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
class TwoWayMap<T, K> {
private forwardMap: Map<T, K>;
private reverseMap: Map<K, T>;
constructor() {
this.forwardMap = new Map<T, K>();
this.reverseMap = new Map<K, T>();
}
get(input: T): K {
return this.forwardMap.get(input);
}
reverseGet(input: K): T {
return this.reverseMap.get(input);
}
set(input: T, input2: K): TwoWayMap<T, K> {
this.forwardMap.set(input, input2);
this.reverseMap.set(input2, input);
return this;
}
}
export class ProfilerService implements IProfilerService {
public _serviceBrand: any;
private _providers = new Map<string, data.IProfilerProvider>();
private _idMap = new TwoWayMap<ProfilerSessionID, string>();
private _sessionMap = new Map<ProfilerSessionID, IProfilerSession>();
private _dialog: ProfilerColumnEditorDialog;
constructor(
@IConnectionManagementService private _connectionService: IConnectionManagementService,
@IConfigurationService public _configurationService: IConfigurationService,
@IInstantiationService private _instantiationService: IInstantiationService
) { }
public registerProvider(providerId: string, provider: data.IProfilerProvider): void {
this._providers.set(providerId, provider);
}
public registerSession(uri: string, session: IProfilerSession): ProfilerSessionID {
this._sessionMap.set(uri, session);
this._idMap.set(uri, uri);
return uri;
}
public onMoreRows(params: data.IProfilerMoreRowsNotificationParams): void {
this._sessionMap.get(this._idMap.reverseGet(params.uri)).onMoreRows(params.rowCount, params.data);
}
public connectSession(id: ProfilerSessionID): Thenable<boolean> {
return this._runAction(id, provider => provider.connectSession(this._idMap.get(id)));
}
public disconnectSession(id: ProfilerSessionID): Thenable<boolean> {
return this._runAction(id, provider => provider.disconnectSession(this._idMap.get(id)));
}
public startSession(id: ProfilerSessionID): Thenable<boolean> {
return this._runAction(id, provider => provider.startSession(this._idMap.get(id)));
}
public pauseSession(id: ProfilerSessionID): Thenable<boolean> {
return this._runAction(id, provider => provider.pauseSession(this._idMap.get(id)));
}
public stopSession(id: ProfilerSessionID): Thenable<boolean> {
return this._runAction(id, provider => provider.stopSession(this._idMap.get(id)));
}
private _runAction<T>(id: ProfilerSessionID, action: (handler: data.IProfilerProvider) => Thenable<T>): Thenable<T> {
// let providerId = this._connectionService.getProviderIdFromUri(this._idMap.get(id));
let providerId = 'default';
if (!providerId) {
return TPromise.wrapError(new Error('Connection is required in order to interact with queries'));
}
let handler = this._providers.get(providerId);
if (handler) {
return action(handler);
} else {
return TPromise.wrapError(new Error('No Handler Registered'));
}
}
public getSessionTemplates(provider?: string): Array<IProfilerSessionTemplate> {
let config = <IProfilerSettings>this._configurationService.getConfiguration(PROFILER_SETTINGS);
if (provider) {
return config.sessionTemplates;
} else {
return config.sessionTemplates;
}
}
public launchColumnEditor(input?: ProfilerInput): Thenable<void> {
if (!this._dialog) {
this._dialog = this._instantiationService.createInstance(ProfilerColumnEditorDialog);
this._dialog.render();
}
this._dialog.open(input);
return TPromise.as(null);
}
}

View File

@@ -0,0 +1,100 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IProfilerService } from './interfaces';
import { TPromise } from 'vs/base/common/winjs.base';
import * as pfs from 'vs/base/node/pfs';
import * as path from 'path';
import * as data from 'data';
declare var __dirname;
const columns = [
'EventClass',
'TextData',
'ApplicationName',
'NTUserName',
'LoginName',
'CPU',
'Reads',
'Writes',
'Duration',
'ClientProcessID',
'SPID',
'StartTime',
'EndTime',
'BinaryData'
];
export class ProfilerTestBackend implements data.IProfilerProvider {
private index = 0;
private timeOutMap = new Map<string, number>();
private testData: Array<Array<string>> = new Array<Array<string>>();
constructor(
@IProfilerService private _profilerService: IProfilerService) { }
startSession(guid: string): Thenable<boolean> {
this.timeOutMap.set(guid, this.intervalFn(guid));
return TPromise.as(true);
}
private intervalFn(guid: string): number {
return setTimeout(() => {
let data = this.testData[this.index++];
let formattedData = {
EventClass: data[0].trim()
};
for (let i = 1; i < data.length; i++) {
formattedData[columns[i]] = data[i];
}
this._profilerService.onMoreRows({ uri: guid, rowCount: 1, data: formattedData });
if (this.index >= this.testData.length) {
this.index = 0;
}
this.timeOutMap.set(guid, this.intervalFn(guid));
}, Math.floor(Math.random() * 1000) + 300);
}
stopSession(guid: string): Thenable<boolean> {
clearTimeout(this.timeOutMap.get(guid));
this.index = 0;
return TPromise.as(true);
}
pauseSession(guid: string): Thenable<boolean> {
clearTimeout(this.timeOutMap.get(guid));
return TPromise.as(true);
}
connectSession(): Thenable<boolean> {
if (this.testData.length === 0) {
return new TPromise<boolean>((resolve, reject) => {
pfs.readFile(path.join(__dirname, 'testData.tsv')).then(result => {
let tabsep = result.toString().split('\t');
for (let i = 0; i < tabsep.length; i++) {
if (i % columns.length === 0) {
this.testData[i / columns.length] = new Array<string>();
}
this.testData[Math.floor(i / columns.length)][i % columns.length] = tabsep[i];
};
resolve(true);
});
});
} else {
return TPromise.as(true);
}
}
disconnectSession(guid: string): Thenable<boolean> {
clearTimeout(this.timeOutMap.get(guid));
this.index = 0;
return TPromise.as(true);
}
}

File diff suppressed because it is too large Load Diff