mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-29 17:23:25 -05:00
move code from parts to contrib (#8319)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.065 13H15v2H2.056v-2h5.009zm3.661-12H7.385L8.44 2.061 7.505 3H15V1h-4.274zM3.237 9H2.056v2H15V9H3.237zm4.208-4l.995 1-.995 1H15V5H7.445z" fill="#424242"/><path d="M5.072 4.03L7.032 6 5.978 7.061l-1.96-1.97-1.961 1.97L1 6l1.96-1.97L1 2.061 2.056 1l1.96 1.97L5.977 1l1.057 1.061L5.072 4.03z" fill="#A1260D"/></svg>
|
||||
|
After Width: | Height: | Size: 419 B |
@@ -0,0 +1 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.065 13H15v2H2.056v-2h5.009zm3.661-12H7.385L8.44 2.061 7.505 3H15V1h-4.274zM3.237 9H2.056v2H15V9H3.237zm4.208-4l.995 1-.995 1H15V5H7.445z" fill="#C5C5C5"/><path d="M5.072 4.03L7.032 6 5.978 7.061l-1.96-1.97-1.961 1.97L1 6l1.96-1.97L1 2.061 2.056 1l1.96 1.97L5.977 1l1.057 1.061L5.072 4.03z" fill="#F48771"/></svg>
|
||||
|
After Width: | Height: | Size: 419 B |
@@ -0,0 +1,21 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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;
|
||||
}
|
||||
|
||||
.vs .codicon.clear-results {
|
||||
background-image: url('clear.svg');
|
||||
}
|
||||
|
||||
.vs-dark .codicon.clear-results {
|
||||
background-image: url('clear_inverse.svg');
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.profiler-filter-dialog {
|
||||
height: 300px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.profiler-filter-clause-table {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.clause-table-container{
|
||||
max-height: 270px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.profiler-filter-remove-condition {
|
||||
width:20px;
|
||||
height:20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.profiler-filter-clause-table-action {
|
||||
cursor: pointer;
|
||||
margin-right: 10px;
|
||||
padding: 2px;
|
||||
text-decoration: underline;
|
||||
display: inline-block;
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
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/workbench/contrib/profiler/browser/profilerInput';
|
||||
import { ProfilerEditor } from 'sql/workbench/contrib/profiler/browser/profilerEditor';
|
||||
import { PROFILER_VIEW_TEMPLATE_SETTINGS, PROFILER_SESSION_TEMPLATE_SETTINGS, IProfilerViewTemplate, IProfilerSessionTemplate, EngineType, PROFILER_FILTER_SETTINGS } from 'sql/workbench/services/profiler/browser/interfaces';
|
||||
|
||||
const profilerDescriptor = new EditorDescriptor(
|
||||
ProfilerEditor,
|
||||
ProfilerEditor.ID,
|
||||
'ProfilerEditor'
|
||||
);
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
|
||||
.registerEditor(profilerDescriptor, [new SyncDescriptor(ProfilerInput)]);
|
||||
|
||||
const profilerViewTemplateSchema: IJSONSchema = {
|
||||
description: nls.localize('profiler.settings.viewTemplates', "Specifies view templates"),
|
||||
type: 'array',
|
||||
items: <IJSONSchema>{
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
},
|
||||
default: <Array<IProfilerViewTemplate>>[
|
||||
{
|
||||
name: 'Standard View',
|
||||
columns: [
|
||||
{
|
||||
name: 'EventClass',
|
||||
eventsMapped: ['name']
|
||||
},
|
||||
{
|
||||
name: 'TextData',
|
||||
eventsMapped: ['options_text', 'batch_text', 'statement']
|
||||
},
|
||||
{
|
||||
name: 'ApplicationName',
|
||||
width: '1',
|
||||
eventsMapped: ['client_app_name']
|
||||
},
|
||||
{
|
||||
name: 'NTUserName',
|
||||
eventsMapped: ['nt_username']
|
||||
},
|
||||
{
|
||||
name: 'LoginName',
|
||||
eventsMapped: ['server_principal_name']
|
||||
},
|
||||
{
|
||||
name: 'ClientProcessID',
|
||||
eventsMapped: ['client_pid']
|
||||
},
|
||||
{
|
||||
name: 'SPID',
|
||||
eventsMapped: ['session_id']
|
||||
},
|
||||
{
|
||||
name: 'StartTime',
|
||||
eventsMapped: ['timestamp']
|
||||
},
|
||||
{
|
||||
name: 'CPU',
|
||||
eventsMapped: ['cpu_time']
|
||||
},
|
||||
{
|
||||
name: 'Reads',
|
||||
eventsMapped: ['logical_reads']
|
||||
},
|
||||
{
|
||||
name: 'Writes',
|
||||
eventsMapped: ['writes']
|
||||
},
|
||||
{
|
||||
name: 'Duration',
|
||||
eventsMapped: ['duration']
|
||||
},
|
||||
{
|
||||
name: 'DatabaseID',
|
||||
eventsMapped: ['database_id']
|
||||
},
|
||||
{
|
||||
name: 'DatabaseName',
|
||||
eventsMapped: ['database_name']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'TSQL View',
|
||||
columns: [
|
||||
{
|
||||
name: 'EventClass',
|
||||
eventsMapped: ['name']
|
||||
},
|
||||
{
|
||||
name: 'TextData',
|
||||
eventsMapped: ['options_text', 'batch_text', 'statement']
|
||||
},
|
||||
{
|
||||
name: 'SPID',
|
||||
eventsMapped: ['session_id']
|
||||
},
|
||||
{
|
||||
name: 'StartTime',
|
||||
eventsMapped: ['timestamp']
|
||||
},
|
||||
{
|
||||
name: 'DatabaseID',
|
||||
eventsMapped: ['database_id']
|
||||
},
|
||||
{
|
||||
name: 'DatabaseName',
|
||||
eventsMapped: ['database_name']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Tuning View',
|
||||
columns: [
|
||||
{
|
||||
name: 'EventClass',
|
||||
eventsMapped: ['name']
|
||||
},
|
||||
{
|
||||
name: 'TextData',
|
||||
eventsMapped: ['options_text', 'batch_text', 'statement']
|
||||
},
|
||||
{
|
||||
name: 'Duration',
|
||||
eventsMapped: ['duration']
|
||||
},
|
||||
{
|
||||
name: 'SPID',
|
||||
eventsMapped: ['session_id']
|
||||
},
|
||||
{
|
||||
name: 'DatabaseID',
|
||||
eventsMapped: ['database_id']
|
||||
},
|
||||
{
|
||||
name: 'DatabaseName',
|
||||
eventsMapped: ['database_name']
|
||||
},
|
||||
{
|
||||
name: 'ObjectType',
|
||||
eventsMapped: ['object_type']
|
||||
},
|
||||
{
|
||||
name: 'LoginName',
|
||||
eventsMapped: ['server_principal_name']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'TSQL_Locks View',
|
||||
columns: [
|
||||
{
|
||||
name: 'EventClass',
|
||||
eventsMapped: ['name']
|
||||
},
|
||||
{
|
||||
name: 'TextData',
|
||||
eventsMapped: ['options_text', 'batch_text', 'statement']
|
||||
},
|
||||
{
|
||||
name: 'ApplicationName',
|
||||
eventsMapped: ['client_app_name']
|
||||
},
|
||||
{
|
||||
name: 'NTUserName',
|
||||
eventsMapped: ['nt_username']
|
||||
},
|
||||
{
|
||||
name: 'LoginName',
|
||||
eventsMapped: ['server_principal_name']
|
||||
},
|
||||
{
|
||||
name: 'ClientProcessID',
|
||||
eventsMapped: ['client_pid']
|
||||
},
|
||||
{
|
||||
name: 'SPID',
|
||||
eventsMapped: ['session_id']
|
||||
},
|
||||
{
|
||||
name: 'StartTime',
|
||||
eventsMapped: ['timestamp']
|
||||
},
|
||||
{
|
||||
name: 'CPU',
|
||||
eventsMapped: ['cpu_time']
|
||||
},
|
||||
{
|
||||
name: 'Reads',
|
||||
eventsMapped: ['logical_reads']
|
||||
},
|
||||
{
|
||||
name: 'Writes',
|
||||
eventsMapped: ['writes']
|
||||
},
|
||||
{
|
||||
name: 'Duration',
|
||||
eventsMapped: ['duration']
|
||||
},
|
||||
{
|
||||
name: 'DatabaseID',
|
||||
eventsMapped: ['database_id']
|
||||
},
|
||||
{
|
||||
name: 'DatabaseName',
|
||||
eventsMapped: ['database_name']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'TSQL_Duration View',
|
||||
columns: [
|
||||
{
|
||||
name: 'EventClass',
|
||||
eventsMapped: ['name']
|
||||
},
|
||||
{
|
||||
name: 'Duration',
|
||||
eventsMapped: ['duration']
|
||||
},
|
||||
{
|
||||
name: 'TextData',
|
||||
eventsMapped: ['options_text', 'batch_text', 'statement']
|
||||
},
|
||||
{
|
||||
name: 'SPID',
|
||||
eventsMapped: ['session_id']
|
||||
},
|
||||
{
|
||||
name: 'DatabaseID',
|
||||
eventsMapped: ['database_id']
|
||||
},
|
||||
{
|
||||
name: 'DatabaseName',
|
||||
eventsMapped: ['database_name']
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
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_OnPrem',
|
||||
defaultView: 'Standard View',
|
||||
engineTypes: [EngineType.Standalone],
|
||||
createStatement:
|
||||
`CREATE EVENT SESSION [{sessionName}] ON SERVER
|
||||
ADD EVENT sqlserver.attention(
|
||||
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.nt_username,sqlserver.query_hash,sqlserver.server_principal_name,sqlserver.session_id)
|
||||
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),
|
||||
ADD EVENT sqlserver.existing_connection(SET collect_options_text=(1)
|
||||
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.nt_username,sqlserver.server_principal_name,sqlserver.session_id)),
|
||||
ADD EVENT sqlserver.login(SET collect_options_text=(1)
|
||||
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.nt_username,sqlserver.server_principal_name,sqlserver.session_id)),
|
||||
ADD EVENT sqlserver.logout(
|
||||
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.nt_username,sqlserver.server_principal_name,sqlserver.session_id)),
|
||||
ADD EVENT sqlserver.rpc_completed(
|
||||
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.database_name,sqlserver.nt_username,sqlserver.query_hash,sqlserver.server_principal_name,sqlserver.session_id)
|
||||
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),
|
||||
ADD EVENT sqlserver.sql_batch_completed(
|
||||
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.database_name,sqlserver.nt_username,sqlserver.query_hash,sqlserver.server_principal_name,sqlserver.session_id)
|
||||
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),
|
||||
ADD EVENT sqlserver.sql_batch_starting(
|
||||
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.database_name,sqlserver.nt_username,sqlserver.query_hash,sqlserver.server_principal_name,sqlserver.session_id)
|
||||
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0))))
|
||||
ADD TARGET package0.ring_buffer(SET max_events_limit=(1000),max_memory=(51200))
|
||||
WITH (MAX_MEMORY=8192 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=5 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=PER_CPU,TRACK_CAUSALITY=ON,STARTUP_STATE=OFF)`
|
||||
},
|
||||
{
|
||||
name: 'Standard_Azure',
|
||||
engineTypes: [EngineType.AzureSQLDB],
|
||||
defaultView: 'Standard View',
|
||||
createStatement:
|
||||
`CREATE EVENT SESSION [{sessionName}] ON DATABASE
|
||||
ADD EVENT sqlserver.attention(
|
||||
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.username,sqlserver.query_hash,sqlserver.session_id)
|
||||
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),
|
||||
ADD EVENT sqlserver.existing_connection(SET collect_options_text=(1)
|
||||
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.username,sqlserver.session_id)),
|
||||
ADD EVENT sqlserver.login(SET collect_options_text=(1)
|
||||
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.username,sqlserver.session_id)),
|
||||
ADD EVENT sqlserver.logout(
|
||||
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.username,sqlserver.session_id)),
|
||||
ADD EVENT sqlserver.rpc_completed(
|
||||
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.username,sqlserver.query_hash,sqlserver.session_id)
|
||||
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),
|
||||
ADD EVENT sqlserver.sql_batch_completed(
|
||||
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.username,sqlserver.query_hash,sqlserver.session_id)
|
||||
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),
|
||||
ADD EVENT sqlserver.sql_batch_starting(
|
||||
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.username,sqlserver.query_hash,sqlserver.session_id)
|
||||
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0))))
|
||||
ADD TARGET package0.ring_buffer(SET max_events_limit=(1000),max_memory=(51200))
|
||||
WITH (MAX_MEMORY=8192 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=5 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=PER_CPU,TRACK_CAUSALITY=ON,STARTUP_STATE=OFF)`
|
||||
},
|
||||
{
|
||||
name: 'TSQL_OnPrem',
|
||||
engineTypes: [EngineType.Standalone],
|
||||
defaultView: 'TSQL View',
|
||||
createStatement:
|
||||
`CREATE EVENT SESSION [{sessionName}] ON SERVER
|
||||
ADD EVENT sqlserver.existing_connection(
|
||||
ACTION(package0.event_sequence,sqlserver.session_id,sqlserver.client_hostname)),
|
||||
ADD EVENT sqlserver.login(SET collect_options_text=(1)
|
||||
ACTION(package0.event_sequence,sqlserver.session_id,sqlserver.client_hostname)),
|
||||
ADD EVENT sqlserver.logout(
|
||||
ACTION(package0.event_sequence,sqlserver.session_id)),
|
||||
ADD EVENT sqlserver.rpc_starting(
|
||||
ACTION(package0.event_sequence,sqlserver.session_id,sqlserver.database_id,sqlserver.database_name)
|
||||
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),
|
||||
ADD EVENT sqlserver.sql_batch_starting(
|
||||
ACTION(package0.event_sequence,sqlserver.session_id,sqlserver.database_id,sqlserver.database_name)
|
||||
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0))))
|
||||
ADD TARGET package0.ring_buffer(SET max_events_limit=(1000),max_memory=(51200))
|
||||
WITH (MAX_MEMORY=8192 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=5 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=PER_CPU,TRACK_CAUSALITY=ON,STARTUP_STATE=OFF)`
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const profilerFiltersSchema: IJSONSchema = {
|
||||
description: nls.localize('profiler.settings.Filters', "Profiler Filters"),
|
||||
type: 'array'
|
||||
};
|
||||
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
|
||||
const profilerConfig: IConfigurationNode = {
|
||||
id: 'Profiler',
|
||||
type: 'object',
|
||||
properties: {
|
||||
[PROFILER_VIEW_TEMPLATE_SETTINGS]: profilerViewTemplateSchema,
|
||||
[PROFILER_SESSION_TEMPLATE_SETTINGS]: profilerSessionTemplateSchema,
|
||||
[PROFILER_FILTER_SETTINGS]: profilerFiltersSchema
|
||||
}
|
||||
};
|
||||
|
||||
configurationRegistry.registerConfiguration(profilerConfig);
|
||||
@@ -0,0 +1,99 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { ProfilerInput } from 'sql/workbench/contrib/profiler/browser/profilerInput';
|
||||
import * as TaskUtilities from 'sql/workbench/browser/taskUtilities';
|
||||
import { IProfilerService } from 'sql/workbench/services/profiler/browser/interfaces';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { ProfilerEditor } from 'sql/workbench/contrib/profiler/browser/profilerEditor';
|
||||
import { ObjectExplorerActionsContext } from 'sql/workbench/contrib/objectExplorer/browser/objectExplorerActions';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
|
||||
import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService';
|
||||
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: 'profiler.newProfiler',
|
||||
handler: (accessor: ServicesAccessor, ...args: any[]) => {
|
||||
let connectionProfile: ConnectionProfile = undefined;
|
||||
let instantiationService: IInstantiationService = accessor.get(IInstantiationService);
|
||||
let editorService: IEditorService = accessor.get(IEditorService);
|
||||
let connectionService: IConnectionManagementService = accessor.get(IConnectionManagementService);
|
||||
let objectExplorerService: IObjectExplorerService = accessor.get(IObjectExplorerService);
|
||||
let connectionDialogService: IConnectionDialogService = accessor.get(IConnectionDialogService);
|
||||
let capabilitiesService: ICapabilitiesService = accessor.get(ICapabilitiesService);
|
||||
|
||||
// If a context is available if invoked from the context menu, we will use the connection profiler of the server node
|
||||
if (args && args.length === 1 && args[0] && args[0] instanceof ObjectExplorerActionsContext) {
|
||||
let context = args[0] as ObjectExplorerActionsContext;
|
||||
connectionProfile = ConnectionProfile.fromIConnectionProfile(capabilitiesService, context.connectionProfile);
|
||||
}
|
||||
else {
|
||||
// No context available, we will try to get the current global active connection
|
||||
connectionProfile = TaskUtilities.getCurrentGlobalConnection(objectExplorerService, connectionService, editorService) as ConnectionProfile;
|
||||
}
|
||||
|
||||
let promise;
|
||||
if (connectionProfile) {
|
||||
promise = connectionService.connectIfNotConnected(connectionProfile, 'connection', true);
|
||||
} else {
|
||||
// if still no luck, we will open the Connection dialog and let user connect to a server
|
||||
promise = connectionDialogService.openDialogAndWait(connectionService, { connectionType: 0, showDashboard: false, providers: [mssqlProviderName] }).then((profile) => {
|
||||
connectionProfile = profile as ConnectionProfile;
|
||||
});
|
||||
}
|
||||
|
||||
return promise.then(() => {
|
||||
if (!connectionProfile) {
|
||||
connectionProfile = TaskUtilities.getCurrentGlobalConnection(objectExplorerService, connectionService, editorService) as ConnectionProfile;
|
||||
}
|
||||
|
||||
if (connectionProfile && connectionProfile.providerName === mssqlProviderName) {
|
||||
let profilerInput = instantiationService.createInstance(ProfilerInput, connectionProfile);
|
||||
editorService.openEditor(profilerInput, { pinned: true }, ACTIVE_GROUP).then(() => Promise.resolve(true));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'profiler.newProfiler',
|
||||
weight: KeybindingWeight.BuiltinExtension,
|
||||
when: undefined,
|
||||
primary: KeyMod.Alt | KeyCode.KEY_P,
|
||||
mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KEY_P },
|
||||
handler: CommandsRegistry.getCommand('profiler.newProfiler').handler
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'profiler.toggleStartStop',
|
||||
weight: KeybindingWeight.EditorContrib,
|
||||
when: undefined,
|
||||
primary: KeyMod.Alt | KeyCode.KEY_S,
|
||||
mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KEY_S },
|
||||
handler: (accessor: ServicesAccessor) => {
|
||||
let profilerService: IProfilerService = accessor.get(IProfilerService);
|
||||
let editorService: IEditorService = accessor.get(IEditorService);
|
||||
|
||||
let activeEditor = editorService.activeControl;
|
||||
if (activeEditor instanceof ProfilerEditor) {
|
||||
let profilerInput = activeEditor.input;
|
||||
if (profilerInput.state.isRunning) {
|
||||
return profilerService.stopSession(profilerInput.id);
|
||||
} else {
|
||||
// clear data when profiler is started
|
||||
profilerInput.data.clear();
|
||||
return profilerService.startSession(profilerInput.id, profilerInput.sessionName);
|
||||
}
|
||||
}
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
});
|
||||
329
src/sql/workbench/contrib/profiler/browser/profilerActions.ts
Normal file
329
src/sql/workbench/contrib/profiler/browser/profilerActions.ts
Normal file
@@ -0,0 +1,329 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 'sql/workbench/services/profiler/browser/interfaces';
|
||||
import { IProfilerController } from 'sql/workbench/contrib/profiler/common/interfaces';
|
||||
import { ProfilerInput } from 'sql/workbench/contrib/profiler/browser/profilerInput';
|
||||
import { Task } from 'sql/platform/tasks/browser/tasksRegistry';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { IConnectionManagementService, IConnectionCompletionOptions } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IEditorAction } from 'vs/editor/common/editorCommon';
|
||||
import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export class ProfilerConnect extends Action {
|
||||
private static readonly ConnectText = nls.localize('profilerAction.connect', "Connect");
|
||||
private static readonly DisconnectText = nls.localize('profilerAction.disconnect', "Disconnect");
|
||||
|
||||
public static ID = 'profiler.connect';
|
||||
public static LABEL = ProfilerConnect.ConnectText;
|
||||
|
||||
private _connected: boolean = false;
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IProfilerService private _profilerService: IProfilerService
|
||||
) {
|
||||
super(id, label, 'connect');
|
||||
}
|
||||
|
||||
public run(input: ProfilerInput): Promise<boolean> {
|
||||
this.enabled = false;
|
||||
if (!this._connected) {
|
||||
return Promise.resolve(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 Promise.resolve(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.label = value ? ProfilerConnect.DisconnectText : ProfilerConnect.ConnectText;
|
||||
}
|
||||
|
||||
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, 'sql start');
|
||||
}
|
||||
|
||||
public run(input: ProfilerInput): Promise<boolean> {
|
||||
input.data.clear();
|
||||
return Promise.resolve(this._profilerService.startSession(input.id, input.sessionName));
|
||||
}
|
||||
}
|
||||
|
||||
export class ProfilerCreate extends Action {
|
||||
public static ID = 'profiler.create';
|
||||
public static LABEL = nls.localize('create', "New Session");
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IProfilerService private _profilerService: IProfilerService
|
||||
) {
|
||||
super(id, label, 'add');
|
||||
}
|
||||
|
||||
public run(input: ProfilerInput): Promise<boolean> {
|
||||
return Promise.resolve(this._profilerService.launchCreateSessionDialog(input).then(() => {
|
||||
return true;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
export class ProfilerPause extends Action {
|
||||
private static readonly PauseText = nls.localize('profilerAction.pauseCapture', "Pause");
|
||||
private static readonly ResumeText = nls.localize('profilerAction.resumeCapture', "Resume");
|
||||
private static readonly PauseCssClass = 'sql pause';
|
||||
private static readonly ResumeCssClass = 'sql continue';
|
||||
|
||||
public static ID = 'profiler.pause';
|
||||
public static LABEL = ProfilerPause.PauseText;
|
||||
|
||||
private _paused: boolean = false;
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IProfilerService private _profilerService: IProfilerService
|
||||
) {
|
||||
super(id, label, ProfilerPause.PauseCssClass);
|
||||
}
|
||||
|
||||
public run(input: ProfilerInput): Promise<boolean> {
|
||||
return Promise.resolve(this._profilerService.pauseSession(input.id).then(() => {
|
||||
this.paused = !this._paused;
|
||||
input.state.change({ isPaused: this.paused, isStopped: false, isRunning: !this.paused });
|
||||
return true;
|
||||
}));
|
||||
}
|
||||
|
||||
public set paused(value: boolean) {
|
||||
this._paused = value;
|
||||
this._setClass(value ? ProfilerPause.ResumeCssClass : ProfilerPause.PauseCssClass);
|
||||
this.label = value ? ProfilerPause.ResumeText : ProfilerPause.PauseText;
|
||||
}
|
||||
|
||||
public get paused(): boolean {
|
||||
return this._paused;
|
||||
}
|
||||
}
|
||||
|
||||
export class ProfilerStop extends Action {
|
||||
public static ID = 'profiler.stop';
|
||||
public static LABEL = nls.localize('profilerStop.stop', "Stop");
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IProfilerService private _profilerService: IProfilerService
|
||||
) {
|
||||
super(id, label, 'sql stop');
|
||||
}
|
||||
|
||||
public run(input: ProfilerInput): Promise<boolean> {
|
||||
return Promise.resolve(this._profilerService.stopSession(input.id));
|
||||
}
|
||||
}
|
||||
|
||||
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, 'clear-results');
|
||||
}
|
||||
|
||||
run(input: ProfilerInput): Promise<void> {
|
||||
input.data.clear();
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
export class ProfilerAutoScroll extends Action {
|
||||
private static readonly AutoScrollOnText = nls.localize('profilerAction.autoscrollOn', "Auto Scroll: On");
|
||||
private static readonly AutoScrollOffText = nls.localize('profilerAction.autoscrollOff', "Auto Scroll: Off");
|
||||
private static readonly CheckedCssClass = 'sql checked';
|
||||
|
||||
public static ID = 'profiler.autoscroll';
|
||||
public static LABEL = ProfilerAutoScroll.AutoScrollOnText;
|
||||
|
||||
constructor(id: string, label: string) {
|
||||
super(id, label, ProfilerAutoScroll.CheckedCssClass);
|
||||
}
|
||||
|
||||
run(input: ProfilerInput): Promise<boolean> {
|
||||
this.checked = !this.checked;
|
||||
this.label = this.checked ? ProfilerAutoScroll.AutoScrollOnText : ProfilerAutoScroll.AutoScrollOffText;
|
||||
this._setClass(this.checked ? ProfilerAutoScroll.CheckedCssClass : '');
|
||||
input.state.change({ autoscroll: this.checked });
|
||||
return Promise.resolve(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, 'codicon-chevron-down');
|
||||
}
|
||||
|
||||
public run(input: ProfilerInput): Promise<boolean> {
|
||||
this.collapsed = !this._collapsed;
|
||||
input.state.change({ isPanelCollapsed: this._collapsed });
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
get collapsed(): boolean {
|
||||
return this._collapsed;
|
||||
}
|
||||
|
||||
set collapsed(val: boolean) {
|
||||
this._collapsed = val === false ? false : true;
|
||||
this._setClass(this._collapsed ? 'codicon-chevron-up' : 'codicon-chevron-down');
|
||||
}
|
||||
}
|
||||
|
||||
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): Promise<boolean> {
|
||||
return Promise.resolve(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(): Promise<void> {
|
||||
this.profiler.findNext();
|
||||
return Promise.resolve(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(): Promise<void> {
|
||||
this.profiler.findPrevious();
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
isSupported(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class NewProfilerAction extends Task {
|
||||
public static readonly ID = 'profiler.newProfiler';
|
||||
public static readonly LABEL = nls.localize('profilerAction.newProfiler', "Launch Profiler");
|
||||
public static readonly ICON = 'profile';
|
||||
|
||||
private _connectionProfile: ConnectionProfile;
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: NewProfilerAction.ID,
|
||||
title: NewProfilerAction.LABEL,
|
||||
iconPath: { dark: NewProfilerAction.ICON, light: NewProfilerAction.ICON },
|
||||
iconClass: NewProfilerAction.ICON
|
||||
});
|
||||
}
|
||||
|
||||
public runTask(accessor: ServicesAccessor, profile: IConnectionProfile): Promise<void> {
|
||||
let profilerInput = accessor.get<IInstantiationService>(IInstantiationService).createInstance(ProfilerInput, profile);
|
||||
return accessor.get<IEditorService>(IEditorService).openEditor(profilerInput, { pinned: true }, ACTIVE_GROUP).then(() => {
|
||||
let options: IConnectionCompletionOptions = {
|
||||
params: undefined,
|
||||
saveTheConnection: false,
|
||||
showConnectionDialogOnError: true,
|
||||
showDashboard: false,
|
||||
showFirewallRuleOnError: true
|
||||
};
|
||||
accessor.get<IConnectionManagementService>(IConnectionManagementService).connect(this._connectionProfile, profilerInput.id, options);
|
||||
|
||||
return Promise.resolve(void 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ProfilerFilterSession extends Action {
|
||||
public static ID = 'profiler.filter';
|
||||
public static LABEL = nls.localize('profiler.filter', "Filter…");
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IProfilerService private _profilerService: IProfilerService
|
||||
) {
|
||||
super(id, label, 'filterLabel');
|
||||
}
|
||||
|
||||
public run(input: ProfilerInput): Promise<boolean> {
|
||||
this._profilerService.launchFilterSessionDialog(input);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class ProfilerClearSessionFilter extends Action {
|
||||
public static ID = 'profiler.clearFilter';
|
||||
public static LABEL = nls.localize('profiler.clearFilter', "Clear Filter");
|
||||
|
||||
constructor(
|
||||
id: string, label: string
|
||||
) {
|
||||
super(id, label, 'clear-filter');
|
||||
}
|
||||
|
||||
public run(input: ProfilerInput): Promise<boolean> {
|
||||
input.clearFilter();
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,414 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/profiler';
|
||||
|
||||
import { Modal } from 'sql/workbench/browser/modal/modal';
|
||||
import { attachModalDialogStyler } from 'sql/platform/theme/common/styler';
|
||||
import { ProfilerInput } from 'sql/workbench/contrib/profiler/browser/profilerInput';
|
||||
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
|
||||
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
|
||||
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
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 { attachListStyler } from 'vs/platform/theme/common/styler';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
||||
|
||||
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 {
|
||||
const data = Object.create(null);
|
||||
const 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): Promise<Array<any>> {
|
||||
if (element instanceof EventItem) {
|
||||
return Promise.resolve(element.getChildren());
|
||||
} else if (element instanceof SessionItem) {
|
||||
return Promise.resolve(element.getChildren());
|
||||
} else {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
getParent(tree: ITree, element: any): Promise<any> {
|
||||
if (element instanceof ColumnItem) {
|
||||
return Promise.resolve(element.parent);
|
||||
} else if (element instanceof EventItem) {
|
||||
return Promise.resolve(element.parent);
|
||||
} else if (element instanceof ColumnSortedColumnItem) {
|
||||
return Promise.resolve(element.parent);
|
||||
} else {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
shouldAutoexpand?(tree: ITree, element: any): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class ProfilerColumnEditorDialog extends Modal {
|
||||
|
||||
private _selectBox: SelectBox;
|
||||
private readonly _options = [
|
||||
{ text: nls.localize('eventSort', "Sort by event") },
|
||||
{ text: nls.localize('nameColumn', "Sort by column") }
|
||||
];
|
||||
private _tree: Tree;
|
||||
private _element: SessionItem;
|
||||
private _treeContainer: HTMLElement;
|
||||
|
||||
constructor(
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IAdsTelemetryService telemetryService: IAdsTelemetryService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IContextViewService private _contextViewService: IContextViewService,
|
||||
@IClipboardService clipboardService: IClipboardService,
|
||||
@ILogService logService: ILogService,
|
||||
@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService
|
||||
) {
|
||||
super(nls.localize('profilerColumnDialog.profiler', "Profiler"), TelemetryKeys.Profiler, telemetryService, layoutService, clipboardService, themeService, logService, textResourcePropertiesService, contextKeyService);
|
||||
}
|
||||
|
||||
public render(): void {
|
||||
super.render();
|
||||
this._register(attachModalDialogStyler(this, this._themeService));
|
||||
this.addFooterButton(nls.localize('profilerColumnDialog.ok', "OK"), () => this.onAccept(undefined));
|
||||
this.addFooterButton(nls.localize('profilerColumnDialog.cancel', "Cancel"), () => this.onClose(undefined));
|
||||
}
|
||||
|
||||
protected renderBody(container: HTMLElement): void {
|
||||
const body = DOM.append(container, DOM.$(''));
|
||||
this._selectBox = new SelectBox(this._options, 0, this._contextViewService);
|
||||
this._selectBox.render(body);
|
||||
this._register(this._selectBox.onDidSelect(e => {
|
||||
this._element.changeSort(e.index === 0 ? 'event' : 'column');
|
||||
this._tree.refresh(this._element, true);
|
||||
}));
|
||||
this._treeContainer = DOM.append(body, DOM.$('.profiler-column-tree'));
|
||||
const 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._updateList();
|
||||
}
|
||||
|
||||
protected onAccept(e: StandardKeyboardEvent): void {
|
||||
this._updateInput();
|
||||
super.onAccept(e);
|
||||
}
|
||||
|
||||
// currently not used, this dialog is a work in progress
|
||||
// tracked in issue #1545 https://github.com/Microsoft/azuredatastudio/issues/1545
|
||||
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);
|
||||
*/
|
||||
}
|
||||
|
||||
// currently not used, this dialog is a work in progress
|
||||
// tracked in issue #1545 https://github.com/Microsoft/azuredatastudio/issues/1545
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export function handleCopyRequest(clipboardService: IClipboardService, textResourcePropertiesService: ITextResourcePropertiesService, range: Slick.Range, getCellValue: (row, cell) => string): void {
|
||||
if (range) {
|
||||
let results = '';
|
||||
for (let i = range.fromRow; i <= range.toRow; i++) {
|
||||
for (let j = range.fromCell; j <= range.toCell; j++) {
|
||||
let value = getCellValue(i, j);
|
||||
if (j !== range.toCell) {
|
||||
value += '\t';
|
||||
}
|
||||
results += value;
|
||||
}
|
||||
|
||||
if (i !== range.toRow) {
|
||||
results += textResourcePropertiesService.getEOL(URI.from({ scheme: Schemas.untitled }));
|
||||
}
|
||||
}
|
||||
clipboardService.writeText(results);
|
||||
}
|
||||
}
|
||||
651
src/sql/workbench/contrib/profiler/browser/profilerEditor.ts
Normal file
651
src/sql/workbench/contrib/profiler/browser/profilerEditor.ts
Normal file
@@ -0,0 +1,651 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/workbench/contrib/profiler/browser/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, IProfilerViewTemplate } from 'sql/workbench/services/profiler/browser/interfaces';
|
||||
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
|
||||
import { attachTableStyler, attachTabbedPanelStyler } from 'sql/platform/theme/common/styler';
|
||||
import { IProfilerStateChangedEvent } from 'sql/workbench/contrib/profiler/common/profilerState';
|
||||
import { ProfilerTableEditor, ProfilerTableViewState } from 'sql/workbench/contrib/profiler/browser/profilerTableEditor';
|
||||
import * as Actions from 'sql/workbench/contrib/profiler/browser/profilerActions';
|
||||
import { CONTEXT_PROFILER_EDITOR, PROFILER_TABLE_COMMAND_SEARCH } from 'sql/workbench/contrib/profiler/common/interfaces';
|
||||
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
|
||||
import { textFormatter, slickGridDataItemColumnValueExtractor } from 'sql/base/browser/ui/table/formatters';
|
||||
import { ProfilerResourceEditor } from 'sql/workbench/contrib/profiler/browser/profilerResourceEditor';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
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/browser/editorExtensions';
|
||||
import { IEditorService } 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 { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { CommonFindController, FindStartFocusAction } from 'vs/editor/contrib/find/findController';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
|
||||
import { DARK, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IView, SplitView, Sizing } from 'vs/base/browser/ui/splitview/splitview';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IWorkbenchThemeService, VS_DARK_THEME, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { clamp } from 'vs/base/common/numbers';
|
||||
import { CopyKeybind } from 'sql/base/browser/ui/table/plugins/copyKeybind.plugin';
|
||||
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
|
||||
import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
|
||||
import { handleCopyRequest } from 'sql/workbench/contrib/profiler/browser/profilerCopyHandler';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { find } from 'vs/base/common/arrays';
|
||||
|
||||
class BasicView implements IView {
|
||||
public get element(): HTMLElement {
|
||||
return this._element;
|
||||
}
|
||||
private _onDidChange = new Emitter<number>();
|
||||
public readonly onDidChange: Event<number> = this._onDidChange.event;
|
||||
|
||||
private _collapsed = false;
|
||||
private size: number;
|
||||
private previousSize: number;
|
||||
private _minimumSize: number;
|
||||
public get minimumSize(): number {
|
||||
return this._minimumSize;
|
||||
}
|
||||
|
||||
private _maximumSize: number;
|
||||
public get maximumSize(): number {
|
||||
return this._maximumSize;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private _defaultMinimumSize: number,
|
||||
private _defaultMaximumSize: number,
|
||||
private _layout: (size: number) => void,
|
||||
private _element: HTMLElement,
|
||||
private options: { headersize?: number } = {}
|
||||
) {
|
||||
this._minimumSize = _defaultMinimumSize;
|
||||
this._maximumSize = _defaultMaximumSize;
|
||||
}
|
||||
|
||||
public layout(size: number): void {
|
||||
this.size = size;
|
||||
this._layout(size);
|
||||
}
|
||||
|
||||
public set collapsed(val: boolean) {
|
||||
if (val !== this._collapsed && this.options.headersize) {
|
||||
this._collapsed = val;
|
||||
if (this.collapsed) {
|
||||
this.previousSize = this.size;
|
||||
this._minimumSize = this.options.headersize;
|
||||
this._maximumSize = this.options.headersize;
|
||||
this._onDidChange.fire(undefined);
|
||||
} else {
|
||||
this._maximumSize = this._defaultMaximumSize;
|
||||
this._minimumSize = this._defaultMinimumSize;
|
||||
this._onDidChange.fire(clamp(this.previousSize, this.minimumSize, this.maximumSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public get collapsed(): boolean {
|
||||
return this._collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IDetailData {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export class ProfilerEditor extends BaseEditor {
|
||||
public static readonly ID: string = 'workbench.editor.profiler';
|
||||
|
||||
private _editor: ProfilerResourceEditor;
|
||||
private _editorModel: ITextModel;
|
||||
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 _viewTemplateSelector: SelectBox;
|
||||
private _viewTemplates: Array<IProfilerViewTemplate>;
|
||||
private _sessionSelector: SelectBox;
|
||||
private _sessionsList: Array<string>;
|
||||
|
||||
// Actions
|
||||
private _connectAction: Actions.ProfilerConnect;
|
||||
private _startAction: Actions.ProfilerStart;
|
||||
private _pauseAction: Actions.ProfilerPause;
|
||||
private _stopAction: Actions.ProfilerStop;
|
||||
private _autoscrollAction: Actions.ProfilerAutoScroll;
|
||||
private _createAction: Actions.ProfilerCreate;
|
||||
private _collapsedPanelAction: Actions.ProfilerCollapsablePanelAction;
|
||||
private _filterAction: Actions.ProfilerFilterSession;
|
||||
private _clearFilterAction: Actions.ProfilerClearSessionFilter;
|
||||
|
||||
private _savedTableViewStates = new Map<ProfilerInput, ProfilerTableViewState>();
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IWorkbenchThemeService themeService: IWorkbenchThemeService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IModelService private _modelService: IModelService,
|
||||
@IProfilerService private _profilerService: IProfilerService,
|
||||
@IContextKeyService private _contextKeyService: IContextKeyService,
|
||||
@IContextViewService private _contextViewService: IContextViewService,
|
||||
@IEditorService editorService: IEditorService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IClipboardService private _clipboardService: IClipboardService,
|
||||
@ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService
|
||||
) {
|
||||
super(ProfilerEditor.ID, telemetryService, themeService, storageService);
|
||||
this._profilerEditorContextKey = CONTEXT_PROFILER_EDITOR.bindTo(this._contextKeyService);
|
||||
|
||||
if (editorService) {
|
||||
editorService.overrideOpenEditor((editor, options, group) => {
|
||||
if (this.isVisible() && (editor !== this.input || group !== this.group)) {
|
||||
this.saveEditorViewState();
|
||||
}
|
||||
return {};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected createEditor(parent: HTMLElement): void {
|
||||
this._container = document.createElement('div');
|
||||
this._container.className = 'carbon-profiler';
|
||||
parent.appendChild(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(
|
||||
300,
|
||||
Number.POSITIVE_INFINITY,
|
||||
size => this._profilerTableEditor.layout(new DOM.Dimension(parseFloat(DOM.getComputedStyle(this._body).width), size)),
|
||||
tableContainer
|
||||
), Sizing.Distribute);
|
||||
|
||||
this._panelView = new BasicView(
|
||||
300,
|
||||
Number.POSITIVE_INFINITY,
|
||||
size => this._tabbedPanel.layout(new DOM.Dimension(DOM.getTotalWidth(this._body), size)),
|
||||
paneContainer,
|
||||
{ headersize: 35 }
|
||||
);
|
||||
this._splitView.addView(this._panelView, Sizing.Distribute);
|
||||
}
|
||||
|
||||
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._startAction = this._instantiationService.createInstance(Actions.ProfilerStart, Actions.ProfilerStart.ID, Actions.ProfilerStart.LABEL);
|
||||
this._startAction.enabled = false;
|
||||
this._createAction = this._instantiationService.createInstance(Actions.ProfilerCreate, Actions.ProfilerCreate.ID, Actions.ProfilerCreate.LABEL);
|
||||
this._createAction.enabled = true;
|
||||
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._filterAction = this._instantiationService.createInstance(Actions.ProfilerFilterSession, Actions.ProfilerFilterSession.ID, Actions.ProfilerFilterSession.LABEL);
|
||||
this._filterAction.enabled = true;
|
||||
this._clearFilterAction = this._instantiationService.createInstance(Actions.ProfilerClearSessionFilter, Actions.ProfilerClearSessionFilter.ID, Actions.ProfilerClearSessionFilter.LABEL);
|
||||
this._clearFilterAction.enabled = true;
|
||||
this._viewTemplates = this._profilerService.getViewTemplates();
|
||||
this._viewTemplateSelector = new SelectBox(this._viewTemplates.map(i => i.name), 'Standard View', this._contextViewService);
|
||||
this._viewTemplateSelector.setAriaLabel(nls.localize('profiler.viewSelectAccessibleName', "Select View"));
|
||||
this._register(this._viewTemplateSelector.onDidSelect(e => {
|
||||
if (this.input) {
|
||||
this.input.viewTemplate = find(this._viewTemplates, i => i.name === e.selected);
|
||||
}
|
||||
}));
|
||||
let viewTemplateContainer = document.createElement('div');
|
||||
viewTemplateContainer.style.width = '150px';
|
||||
viewTemplateContainer.style.paddingRight = '5px';
|
||||
this._viewTemplateSelector.render(viewTemplateContainer);
|
||||
|
||||
this._sessionsList = [''];
|
||||
this._sessionSelector = new SelectBox(this._sessionsList, '', this._contextViewService);
|
||||
this._sessionSelector.setAriaLabel(nls.localize('profiler.sessionSelectAccessibleName', "Select Session"));
|
||||
this._register(this._sessionSelector.onDidSelect(e => {
|
||||
if (this.input) {
|
||||
this.input.sessionName = e.selected;
|
||||
}
|
||||
}));
|
||||
let sessionsContainer = document.createElement('div');
|
||||
sessionsContainer.style.minWidth = '150px';
|
||||
sessionsContainer.style.maxWidth = '250px';
|
||||
sessionsContainer.style.paddingRight = '5px';
|
||||
this._sessionSelector.render(sessionsContainer);
|
||||
|
||||
this._register(attachSelectBoxStyler(this._viewTemplateSelector, this.themeService));
|
||||
this._register(attachSelectBoxStyler(this._sessionSelector, this.themeService));
|
||||
|
||||
this._actionBar.setContent([
|
||||
{ action: this._createAction },
|
||||
{ element: Taskbar.createTaskbarSeparator() },
|
||||
{ element: this._createTextElement(nls.localize('profiler.sessionSelectLabel', "Select Session:")) },
|
||||
{ element: sessionsContainer },
|
||||
{ action: this._startAction },
|
||||
{ action: this._stopAction },
|
||||
{ action: this._pauseAction },
|
||||
{ element: Taskbar.createTaskbarSeparator() },
|
||||
{ action: this._filterAction },
|
||||
{ action: this._clearFilterAction },
|
||||
{ element: Taskbar.createTaskbarSeparator() },
|
||||
{ element: this._createTextElement(nls.localize('profiler.viewSelectLabel', "Select View:")) },
|
||||
{ element: viewTemplateContainer },
|
||||
{ action: this._autoscrollAction },
|
||||
{ action: this._instantiationService.createInstance(Actions.ProfilerClear, Actions.ProfilerClear.ID, Actions.ProfilerClear.LABEL) }
|
||||
]);
|
||||
}
|
||||
|
||||
private _createTextElement(text: string): HTMLDivElement {
|
||||
let textElement = document.createElement('div');
|
||||
textElement.style.paddingRight = '10px';
|
||||
textElement.innerText = text;
|
||||
textElement.style.textAlign = 'center';
|
||||
textElement.style.display = 'flex';
|
||||
textElement.style.alignItems = 'center';
|
||||
return textElement;
|
||||
}
|
||||
|
||||
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';
|
||||
let theme = this.themeService.getTheme();
|
||||
if (theme.type === DARK) {
|
||||
DOM.addClass(profilerTableContainer, VS_DARK_THEME);
|
||||
} else if (theme.type === HIGH_CONTRAST) {
|
||||
DOM.addClass(profilerTableContainer, VS_HC_THEME);
|
||||
}
|
||||
this.themeService.onThemeChange(e => {
|
||||
DOM.removeClasses(profilerTableContainer, VS_DARK_THEME, VS_HC_THEME);
|
||||
if (e.type === DARK) {
|
||||
DOM.addClass(profilerTableContainer, VS_DARK_THEME);
|
||||
} else if (e.type === HIGH_CONTRAST) {
|
||||
DOM.addClass(profilerTableContainer, VS_HC_THEME);
|
||||
}
|
||||
});
|
||||
this._profilerTableEditor = this._instantiationService.createInstance(ProfilerTableEditor);
|
||||
this._profilerTableEditor.createEditor(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).filter(key => {
|
||||
return data[key] !== ' ';
|
||||
}).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);
|
||||
attachTabbedPanelStyler(this._tabbedPanel, this.themeService);
|
||||
|
||||
const expandPanel = () => {
|
||||
if (this._collapsedPanelAction.collapsed) {
|
||||
this._collapsedPanelAction.run(this.input);
|
||||
}
|
||||
};
|
||||
|
||||
this._tabbedPanel.pushTab({
|
||||
identifier: 'editor',
|
||||
title: nls.localize('text', "Text"),
|
||||
view: {
|
||||
layout: dim => this._editor.layout(dim),
|
||||
render: parent => parent.appendChild(editorContainer),
|
||||
focus: () => this._editor.focus()
|
||||
},
|
||||
tabSelectedHandler: expandPanel
|
||||
});
|
||||
|
||||
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, {
|
||||
dataProvider: this._detailTableData, columns: [
|
||||
{
|
||||
id: 'label',
|
||||
name: nls.localize('label', "Label"),
|
||||
field: 'label',
|
||||
formatter: textFormatter
|
||||
},
|
||||
{
|
||||
id: 'value',
|
||||
name: nls.localize('profilerEditor.value', "Value"),
|
||||
field: 'value',
|
||||
formatter: textFormatter
|
||||
}
|
||||
]
|
||||
}, {
|
||||
forceFitColumns: true,
|
||||
dataItemColumnValueExtractor: slickGridDataItemColumnValueExtractor
|
||||
});
|
||||
|
||||
this._detailTableData.onRowCountChange(() => {
|
||||
this._detailTable.updateRowCount();
|
||||
});
|
||||
|
||||
const detailTableCopyKeybind = new CopyKeybind();
|
||||
detailTableCopyKeybind.onCopy((ranges: Slick.Range[]) => {
|
||||
// we always only get 1 item in the ranges
|
||||
if (ranges && ranges.length === 1) {
|
||||
handleCopyRequest(this._clipboardService, this.textResourcePropertiesService, ranges[0], (row, cell) => {
|
||||
const item = this._detailTableData.getItem(row);
|
||||
// only 2 columns in this table
|
||||
return cell === 0 ? item.label : item.value;
|
||||
});
|
||||
}
|
||||
});
|
||||
this._detailTable.setSelectionModel(new CellSelectionModel());
|
||||
this._detailTable.registerPlugin(detailTableCopyKeybind);
|
||||
|
||||
|
||||
this._tabbedPanel.pushTab({
|
||||
identifier: 'detailTable',
|
||||
title: nls.localize('details', "Details"),
|
||||
view: {
|
||||
layout: dim => this._detailTable.layout(dim),
|
||||
render: parent => parent.appendChild(detailTableContainer),
|
||||
focus: () => this._detailTable.focus()
|
||||
},
|
||||
tabSelectedHandler: expandPanel
|
||||
});
|
||||
|
||||
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(editorContainer);
|
||||
this._editor.setVisible(true);
|
||||
this._editorInput = this._instantiationService.createInstance(UntitledEditorInput, URI.from({ scheme: Schemas.untitled }), 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): Promise<void> {
|
||||
let savedViewState = this._savedTableViewStates.get(input);
|
||||
|
||||
this._profilerEditorContextKey.set(true);
|
||||
if (input instanceof ProfilerInput && input.matches(this.input)) {
|
||||
if (savedViewState) {
|
||||
this._profilerTableEditor.restoreViewState(savedViewState);
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
return super.setInput(input, options, CancellationToken.None).then(() => {
|
||||
this._profilerTableEditor.setInput(input);
|
||||
|
||||
if (input.viewTemplate) {
|
||||
this._viewTemplateSelector.selectWithOptionName(input.viewTemplate.name);
|
||||
} else {
|
||||
input.viewTemplate = find(this._viewTemplates, i => i.name === 'Standard View');
|
||||
}
|
||||
|
||||
this._actionBar.context = input;
|
||||
this._tabbedPanel.actionBarContext = input;
|
||||
if (this._stateListener) {
|
||||
this._stateListener.dispose();
|
||||
}
|
||||
this._stateListener = input.state.onProfilerStateChange(e => this._onStateChange(e));
|
||||
this._onStateChange({
|
||||
isConnected: true,
|
||||
isRunning: true,
|
||||
isPaused: true,
|
||||
isStopped: true,
|
||||
autoscroll: true,
|
||||
isPanelCollapsed: true
|
||||
});
|
||||
this._profilerTableEditor.updateState();
|
||||
this._profilerTableEditor.focus();
|
||||
if (savedViewState) {
|
||||
this._profilerTableEditor.restoreViewState(savedViewState);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public clearInput(): void {
|
||||
this._profilerEditorContextKey.set(false);
|
||||
}
|
||||
|
||||
public toggleSearch(): void {
|
||||
if (this._editor.getControl().hasTextFocus()) {
|
||||
let editor = this._editor.getControl() as ICodeEditor;
|
||||
let controller = CommonFindController.get(editor);
|
||||
if (controller) {
|
||||
controller.start({
|
||||
forceRevealReplace: false,
|
||||
seedSearchStringFromGlobalClipboard: false,
|
||||
seedSearchStringFromSelection: (controller.getState().searchString.length === 0),
|
||||
shouldFocus: FindStartFocusAction.FocusFindInput,
|
||||
shouldAnimate: true,
|
||||
updateSearchScope: false
|
||||
});
|
||||
}
|
||||
} 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;
|
||||
|
||||
if (this.input.state.isConnected) {
|
||||
this._updateToolbar();
|
||||
|
||||
// Launch the create session dialog if openning a new window.
|
||||
let uiState = this._profilerService.getSessionViewState(this.input.id);
|
||||
let previousSessionName = uiState && uiState.previousSessionName;
|
||||
if (!this.input.sessionName && !previousSessionName) {
|
||||
this._profilerService.launchCreateSessionDialog(this.input);
|
||||
}
|
||||
|
||||
this._updateSessionSelector(previousSessionName);
|
||||
} else {
|
||||
this._startAction.enabled = false;
|
||||
this._stopAction.enabled = false;
|
||||
this._pauseAction.enabled = false;
|
||||
this._sessionSelector.setOptions([]);
|
||||
this._sessionSelector.disable();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.isPaused) {
|
||||
this._pauseAction.paused = this.input.state.isPaused;
|
||||
this._updateToolbar();
|
||||
}
|
||||
|
||||
if (e.isStopped || e.isRunning) {
|
||||
if (this.input.state.isRunning) {
|
||||
this._updateToolbar();
|
||||
this._sessionSelector.setOptions([this.input.sessionName]);
|
||||
this._sessionSelector.selectWithOptionName(this.input.sessionName);
|
||||
this._sessionSelector.disable();
|
||||
this._viewTemplateSelector.selectWithOptionName(this.input.viewTemplate.name);
|
||||
}
|
||||
if (this.input.state.isStopped) {
|
||||
this._updateToolbar();
|
||||
this._updateSessionSelector();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _updateSessionSelector(previousSessionName: string = undefined) {
|
||||
this._sessionSelector.enable();
|
||||
this._profilerService.getXEventSessions(this.input.id).then((r) => {
|
||||
if (!r) {
|
||||
r = [];
|
||||
}
|
||||
|
||||
this._sessionSelector.setOptions(r);
|
||||
this._sessionsList = r;
|
||||
if (this._sessionsList.length > 0) {
|
||||
if (!this.input.sessionName) {
|
||||
this.input.sessionName = previousSessionName;
|
||||
}
|
||||
|
||||
if (this._sessionsList.indexOf(this.input.sessionName) === -1) {
|
||||
this.input.sessionName = this._sessionsList[0];
|
||||
}
|
||||
|
||||
this._sessionSelector.selectWithOptionName(this.input.sessionName);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private _updateToolbar(): void {
|
||||
this._startAction.enabled = !this.input.state.isRunning && !this.input.state.isPaused && this.input.state.isConnected;
|
||||
this._createAction.enabled = !this.input.state.isRunning && !this.input.state.isPaused && this.input.state.isConnected;
|
||||
this._stopAction.enabled = !this.input.state.isStopped && (this.input.state.isRunning || this.input.state.isPaused) && this.input.state.isConnected;
|
||||
this._pauseAction.enabled = !this.input.state.isStopped && (this.input.state.isRunning || this.input.state.isPaused && this.input.state.isConnected);
|
||||
}
|
||||
|
||||
public layout(dimension: DOM.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));
|
||||
}
|
||||
|
||||
private saveEditorViewState(): void {
|
||||
if (this.input && this._profilerTableEditor) {
|
||||
this._savedTableViewStates.set(this.input, this._profilerTableEditor.saveViewState());
|
||||
}
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this._profilerEditorContextKey.set(true);
|
||||
super.focus();
|
||||
let savedViewState = this._savedTableViewStates.get(this.input);
|
||||
if (savedViewState) {
|
||||
this._profilerTableEditor.restoreViewState(savedViewState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class SettingsCommand extends Command {
|
||||
|
||||
protected getProfilerEditor(accessor: ServicesAccessor): ProfilerEditor {
|
||||
const activeEditor = accessor.get(IEditorService).activeControl;
|
||||
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,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
});
|
||||
command.register();
|
||||
@@ -0,0 +1,348 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/profilerFilterDialog';
|
||||
import { Button } from 'sql/base/browser/ui/button/button';
|
||||
import { Modal } from 'sql/workbench/browser/modal/modal';
|
||||
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
|
||||
import { attachButtonStyler, attachModalDialogStyler, attachInputBoxStyler } from 'sql/platform/theme/common/styler';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ProfilerInput } from 'sql/workbench/contrib/profiler/browser/profilerInput';
|
||||
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
|
||||
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
|
||||
import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { ProfilerFilter, ProfilerFilterClause, ProfilerFilterClauseOperator, IProfilerService } from 'sql/workbench/services/profiler/browser/interfaces';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { find, firstIndex } from 'vs/base/common/arrays';
|
||||
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
||||
|
||||
|
||||
const ClearText: string = localize('profilerFilterDialog.clear', "Clear all");
|
||||
const ApplyText: string = localize('profilerFilterDialog.apply', "Apply");
|
||||
const OkText: string = localize('profilerFilterDialog.ok', "OK");
|
||||
const CancelText: string = localize('profilerFilterDialog.cancel', "Cancel");
|
||||
const DialogTitle: string = localize('profilerFilterDialog.title', "Filters");
|
||||
const RemoveText: string = localize('profilerFilterDialog.remove', "Remove this clause");
|
||||
const SaveFilterText: string = localize('profilerFilterDialog.saveFilter', "Save Filter");
|
||||
const LoadFilterText: string = localize('profilerFilterDialog.loadFilter', "Load Filter");
|
||||
const AddClauseText: string = localize('profilerFilterDialog.addClauseText', "Add a clause");
|
||||
const TitleIconClass: string = 'icon filterLabel';
|
||||
|
||||
const FieldText: string = localize('profilerFilterDialog.fieldColumn', "Field");
|
||||
const OperatorText: string = localize('profilerFilterDialog.operatorColumn', "Operator");
|
||||
const ValueText: string = localize('profilerFilterDialog.valueColumn', "Value");
|
||||
|
||||
const Equals: string = '=';
|
||||
const NotEquals: string = '<>';
|
||||
const LessThan: string = '<';
|
||||
const LessThanOrEquals: string = '<=';
|
||||
const GreaterThan: string = '>';
|
||||
const GreaterThanOrEquals: string = '>=';
|
||||
const IsNull: string = localize('profilerFilterDialog.isNullOperator', "Is Null");
|
||||
const IsNotNull: string = localize('profilerFilterDialog.isNotNullOperator', "Is Not Null");
|
||||
const Contains: string = localize('profilerFilterDialog.containsOperator', "Contains");
|
||||
const NotContains: string = localize('profilerFilterDialog.notContainsOperator', "Not Contains");
|
||||
const StartsWith: string = localize('profilerFilterDialog.startsWithOperator', "Starts With");
|
||||
const NotStartsWith: string = localize('profilerFilterDialog.notStartsWithOperator', "Not Starts With");
|
||||
|
||||
const Operators = [Equals, NotEquals, LessThan, LessThanOrEquals, GreaterThan, GreaterThanOrEquals, GreaterThan, GreaterThanOrEquals, IsNull, IsNotNull, Contains, NotContains, StartsWith, NotStartsWith];
|
||||
|
||||
export class ProfilerFilterDialog extends Modal {
|
||||
|
||||
private _clauseBuilder: HTMLElement;
|
||||
private _okButton: Button;
|
||||
private _cancelButton: Button;
|
||||
private _applyButton: Button;
|
||||
private _loadFilterButton: Button;
|
||||
private _saveFilterButton: Button;
|
||||
private _input: ProfilerInput;
|
||||
private _clauseRows: ClauseRowUI[] = [];
|
||||
|
||||
|
||||
constructor(
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IClipboardService clipboardService: IClipboardService,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@IAdsTelemetryService telemetryService: IAdsTelemetryService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@ILogService logService: ILogService,
|
||||
@IContextViewService private contextViewService: IContextViewService,
|
||||
@IProfilerService private profilerService: IProfilerService,
|
||||
@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService
|
||||
) {
|
||||
super('', TelemetryKeys.ProfilerFilter, telemetryService, layoutService, clipboardService, themeService, logService, textResourcePropertiesService, contextKeyService, { isFlyout: false, hasTitleIcon: true });
|
||||
}
|
||||
|
||||
public open(input: ProfilerInput) {
|
||||
this._input = input;
|
||||
this.render();
|
||||
this.show();
|
||||
this._okButton.focus();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
|
||||
}
|
||||
|
||||
public render() {
|
||||
super.render();
|
||||
this.title = DialogTitle;
|
||||
this.titleIconClassName = TitleIconClass;
|
||||
this._register(attachModalDialogStyler(this, this._themeService));
|
||||
this._saveFilterButton = this.addFooterButton(SaveFilterText, () => this.saveFilter(), 'left');
|
||||
this._loadFilterButton = this.addFooterButton(LoadFilterText, () => this.loadSavedFilter(), 'left');
|
||||
this._applyButton = this.addFooterButton(ApplyText, () => this.filterSession());
|
||||
this._okButton = this.addFooterButton(OkText, () => this.handleOkButtonClick());
|
||||
this._cancelButton = this.addFooterButton(CancelText, () => this.hide());
|
||||
this._register(attachButtonStyler(this._okButton, this._themeService));
|
||||
this._register(attachButtonStyler(this._cancelButton, this._themeService));
|
||||
this._register(attachButtonStyler(this._applyButton, this._themeService));
|
||||
this._register(attachButtonStyler(this._saveFilterButton, this._themeService));
|
||||
this._register(attachButtonStyler(this._loadFilterButton, this._themeService));
|
||||
}
|
||||
|
||||
protected renderBody(container: HTMLElement) {
|
||||
const body = DOM.append(container, DOM.$('.profiler-filter-dialog'));
|
||||
const clauseTableContainer = DOM.append(body, DOM.$('.clause-table-container'));
|
||||
this._clauseBuilder = DOM.append(clauseTableContainer, DOM.$('table.profiler-filter-clause-table'));
|
||||
const headerRow = DOM.append(this._clauseBuilder, DOM.$('tr'));
|
||||
DOM.append(headerRow, DOM.$('td')).innerText = FieldText;
|
||||
DOM.append(headerRow, DOM.$('td')).innerText = OperatorText;
|
||||
DOM.append(headerRow, DOM.$('td')).innerText = ValueText;
|
||||
DOM.append(headerRow, DOM.$('td')).innerText = '';
|
||||
|
||||
this._input.filter.clauses.forEach(clause => {
|
||||
this.addClauseRow(true, clause.field, this.convertToOperatorString(clause.operator), clause.value);
|
||||
});
|
||||
|
||||
this.createClauseTableActionLink(AddClauseText, body, () => { this.addClauseRow(false); });
|
||||
this.createClauseTableActionLink(ClearText, body, () => { this.handleClearButtonClick(); });
|
||||
}
|
||||
|
||||
protected layout(height?: number): void {
|
||||
// Nothing to re-layout
|
||||
}
|
||||
|
||||
/* espace key */
|
||||
protected onClose() {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
/* enter key */
|
||||
protected onAccept() {
|
||||
this.handleOkButtonClick();
|
||||
}
|
||||
|
||||
private handleOkButtonClick(): void {
|
||||
this.filterSession();
|
||||
this.hide();
|
||||
}
|
||||
|
||||
private handleClearButtonClick() {
|
||||
this._clauseRows.forEach(clause => {
|
||||
clause.row.remove();
|
||||
});
|
||||
this._clauseRows = [];
|
||||
}
|
||||
|
||||
private createClauseTableActionLink(text: string, parent: HTMLElement, handler: () => void): void {
|
||||
const actionLink = DOM.append(parent, DOM.$('.profiler-filter-clause-table-action', {
|
||||
'tabIndex': '0',
|
||||
'role': 'button'
|
||||
}));
|
||||
actionLink.innerText = text;
|
||||
DOM.addDisposableListener(actionLink, DOM.EventType.CLICK, handler);
|
||||
DOM.addStandardDisposableListener(actionLink, DOM.EventType.KEY_DOWN, (e: StandardKeyboardEvent) => {
|
||||
if (e.equals(KeyCode.Space) || e.equals(KeyCode.Enter)) {
|
||||
handler();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private createSelectBox(container: HTMLElement, options: string[], selectedOption: string, ariaLabel: string): SelectBox {
|
||||
const dropdown = new SelectBox(options, selectedOption, this.contextViewService, undefined, { ariaLabel: ariaLabel });
|
||||
dropdown.render(container);
|
||||
this._register(attachSelectBoxStyler(dropdown, this._themeService));
|
||||
return dropdown;
|
||||
}
|
||||
|
||||
private filterSession(): void {
|
||||
this._input.filterSession(this.getFilter());
|
||||
}
|
||||
|
||||
private saveFilter(): void {
|
||||
this.profilerService.saveFilter(this.getFilter());
|
||||
}
|
||||
|
||||
private loadSavedFilter(): void {
|
||||
// for now we only have one saved filter, this is enough for what user asked for so far.
|
||||
const savedFilters = this.profilerService.getFilters();
|
||||
if (savedFilters && savedFilters.length > 0) {
|
||||
const savedFilter = savedFilters[0];
|
||||
this._clauseRows.forEach(clause => {
|
||||
clause.row.remove();
|
||||
});
|
||||
this._clauseRows = [];
|
||||
savedFilter.clauses.forEach(clause => {
|
||||
this.addClauseRow(true, clause.field, this.convertToOperatorString(clause.operator), clause.value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private getFilter(): ProfilerFilter {
|
||||
const clauses: ProfilerFilterClause[] = [];
|
||||
|
||||
this._clauseRows.forEach(row => {
|
||||
clauses.push({
|
||||
field: row.field.value,
|
||||
operator: this.convertToOperatorEnum(row.operator.value),
|
||||
value: row.value.value
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
name: 'default',
|
||||
clauses: clauses
|
||||
};
|
||||
}
|
||||
|
||||
private addClauseRow(setInitialValue: boolean, field?: string, operator?: string, value?: string): void {
|
||||
const columns = this._input.columns.map(column => column.name);
|
||||
if (field && !find(columns, x => x === field)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const row = DOM.append(this._clauseBuilder, DOM.$('tr'));
|
||||
const clauseId = generateUuid();
|
||||
|
||||
const fieldDropDown = this.createSelectBox(DOM.append(row, DOM.$('td')), columns, columns[0], FieldText);
|
||||
|
||||
const operatorDropDown = this.createSelectBox(DOM.append(row, DOM.$('td')), Operators, Operators[0], OperatorText);
|
||||
|
||||
const valueText = new InputBox(DOM.append(row, DOM.$('td')), undefined, {});
|
||||
this._register(attachInputBoxStyler(valueText, this._themeService));
|
||||
|
||||
const removeCell = DOM.append(row, DOM.$('td'));
|
||||
const removeClauseButton = DOM.append(removeCell, DOM.$('.profiler-filter-remove-condition.codicon.remove', {
|
||||
'tabIndex': '0',
|
||||
'aria-label': RemoveText,
|
||||
'title': RemoveText,
|
||||
'role': 'button'
|
||||
}));
|
||||
|
||||
DOM.addStandardDisposableListener(removeClauseButton, DOM.EventType.KEY_DOWN, (e: StandardKeyboardEvent) => {
|
||||
if (e.equals(KeyCode.Space) || e.equals(KeyCode.Enter)) {
|
||||
this.removeRow(clauseId);
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
DOM.addDisposableListener(removeClauseButton, DOM.EventType.CLICK, (e: MouseEvent) => {
|
||||
this.removeRow(clauseId);
|
||||
});
|
||||
|
||||
if (setInitialValue) {
|
||||
fieldDropDown.selectWithOptionName(field);
|
||||
operatorDropDown.selectWithOptionName(operator);
|
||||
valueText.value = value;
|
||||
}
|
||||
|
||||
this._clauseRows.push({
|
||||
id: clauseId,
|
||||
row,
|
||||
field: fieldDropDown,
|
||||
operator: operatorDropDown,
|
||||
value: valueText
|
||||
});
|
||||
}
|
||||
|
||||
private removeRow(clauseId: string) {
|
||||
const idx = firstIndex(this._clauseRows, (entry) => { return entry.id === clauseId; });
|
||||
if (idx !== -1) {
|
||||
this._clauseRows[idx].row.remove();
|
||||
this._clauseRows.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
private convertToOperatorEnum(operator: string): ProfilerFilterClauseOperator {
|
||||
switch (operator) {
|
||||
case Equals:
|
||||
return ProfilerFilterClauseOperator.Equals;
|
||||
case NotEquals:
|
||||
return ProfilerFilterClauseOperator.NotEquals;
|
||||
case LessThan:
|
||||
return ProfilerFilterClauseOperator.LessThan;
|
||||
case LessThanOrEquals:
|
||||
return ProfilerFilterClauseOperator.LessThanOrEquals;
|
||||
case GreaterThan:
|
||||
return ProfilerFilterClauseOperator.GreaterThan;
|
||||
case GreaterThanOrEquals:
|
||||
return ProfilerFilterClauseOperator.GreaterThanOrEquals;
|
||||
case IsNull:
|
||||
return ProfilerFilterClauseOperator.IsNull;
|
||||
case IsNotNull:
|
||||
return ProfilerFilterClauseOperator.IsNotNull;
|
||||
case Contains:
|
||||
return ProfilerFilterClauseOperator.Contains;
|
||||
case NotContains:
|
||||
return ProfilerFilterClauseOperator.NotContains;
|
||||
case StartsWith:
|
||||
return ProfilerFilterClauseOperator.StartsWith;
|
||||
case NotStartsWith:
|
||||
return ProfilerFilterClauseOperator.NotStartsWith;
|
||||
default:
|
||||
throw new Error(`Not a valid operator: ${operator}`);
|
||||
}
|
||||
}
|
||||
|
||||
private convertToOperatorString(operator: ProfilerFilterClauseOperator): string {
|
||||
switch (operator) {
|
||||
case ProfilerFilterClauseOperator.Equals:
|
||||
return Equals;
|
||||
case ProfilerFilterClauseOperator.NotEquals:
|
||||
return NotEquals;
|
||||
case ProfilerFilterClauseOperator.LessThan:
|
||||
return LessThan;
|
||||
case ProfilerFilterClauseOperator.LessThanOrEquals:
|
||||
return LessThanOrEquals;
|
||||
case ProfilerFilterClauseOperator.GreaterThan:
|
||||
return GreaterThan;
|
||||
case ProfilerFilterClauseOperator.GreaterThanOrEquals:
|
||||
return GreaterThanOrEquals;
|
||||
case ProfilerFilterClauseOperator.IsNull:
|
||||
return IsNull;
|
||||
case ProfilerFilterClauseOperator.IsNotNull:
|
||||
return IsNotNull;
|
||||
case ProfilerFilterClauseOperator.Contains:
|
||||
return Contains;
|
||||
case ProfilerFilterClauseOperator.NotContains:
|
||||
return NotContains;
|
||||
case ProfilerFilterClauseOperator.StartsWith:
|
||||
return StartsWith;
|
||||
case ProfilerFilterClauseOperator.NotStartsWith:
|
||||
return NotStartsWith;
|
||||
default:
|
||||
throw new Error(`Not a valid operator: ${operator}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ClauseRowUI {
|
||||
id: string;
|
||||
row: HTMLElement;
|
||||
field: SelectBox;
|
||||
operator: SelectBox;
|
||||
value: InputBox;
|
||||
}
|
||||
570
src/sql/workbench/contrib/profiler/browser/profilerFindWidget.ts
Normal file
570
src/sql/workbench/contrib/profiler/browser/profilerFindWidget.ts
Normal file
@@ -0,0 +1,570 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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, CONTEXT_FIND_INPUT_FOCUSED } from 'vs/editor/contrib/find/findModel';
|
||||
import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/findState';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import * as colors 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', "Your search returned a large number of results, only the first 999 matches will be highlighted.");
|
||||
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 PROFILER_MAX_MATCHES = 999;
|
||||
|
||||
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;
|
||||
|
||||
private searchTimeoutHandle: any;
|
||||
|
||||
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.onFindReplaceStateChange((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.onDidFocus(() => {
|
||||
this._findInputFocussed.set(true);
|
||||
});
|
||||
this._focusTracker.onDidBlur(() => {
|
||||
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 >= PROFILER_MAX_MATCHES) {
|
||||
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 >= PROFILER_MAX_MATCHES) {
|
||||
matchesCount = PROFILER_MAX_MATCHES + '+';
|
||||
}
|
||||
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(colors.inputActiveOptionBorder),
|
||||
inputBackground: theme.getColor(colors.inputBackground),
|
||||
inputForeground: theme.getColor(colors.inputForeground),
|
||||
inputBorder: theme.getColor(colors.inputBorder),
|
||||
inputValidationInfoBackground: theme.getColor(colors.inputValidationInfoBackground),
|
||||
inputValidationInfoBorder: theme.getColor(colors.inputValidationInfoBorder),
|
||||
inputValidationWarningBackground: theme.getColor(colors.inputValidationWarningBackground),
|
||||
inputValidationWarningBorder: theme.getColor(colors.inputValidationWarningBorder),
|
||||
inputValidationErrorBackground: theme.getColor(colors.inputValidationErrorBackground),
|
||||
inputValidationErrorBorder: theme.getColor(colors.inputValidationErrorBorder)
|
||||
};
|
||||
this._findInput.style(inputStyles);
|
||||
}
|
||||
|
||||
// ----- Public
|
||||
|
||||
public focusFindInput(): void {
|
||||
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().then(null, onUnexpectedError);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.equals(KeyMod.Shift | KeyCode.Enter)) {
|
||||
this._tableController.getAction(ACTION_IDS.FIND_NEXT).run().then(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, true, {
|
||||
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(() => {
|
||||
let self = this;
|
||||
if (self.searchTimeoutHandle) {
|
||||
clearTimeout(self.searchTimeoutHandle);
|
||||
}
|
||||
|
||||
this.searchTimeoutHandle = setTimeout(function () {
|
||||
self._state.change({ searchString: self._findInput.getValue() }, true);
|
||||
}, 300);
|
||||
}));
|
||||
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().then(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().then(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.onDidStart((e: ISashEvent) => {
|
||||
originalWidth = dom.getTotalWidth(this._domNode);
|
||||
}));
|
||||
|
||||
this._register(this._resizeSash.onDidChange((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 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);
|
||||
}
|
||||
}
|
||||
316
src/sql/workbench/contrib/profiler/browser/profilerInput.ts
Normal file
316
src/sql/workbench/contrib/profiler/browser/profilerInput.ts
Normal file
@@ -0,0 +1,316 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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, IProfilerViewTemplate, ProfilerFilter } from 'sql/workbench/services/profiler/browser/interfaces';
|
||||
import { ProfilerState } from 'sql/workbench/contrib/profiler/common/profilerState';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
import { EditorInput, ConfirmResult } from 'vs/workbench/common/editor';
|
||||
import { IEditorModel } from 'vs/platform/editor/common/editor';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { IDialogService, IShowResult } from 'vs/platform/dialogs/common/dialogs';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { FilterData } from 'sql/workbench/services/profiler/browser/profilerFilter';
|
||||
import { uriPrefixes } from 'sql/platform/connection/common/utils';
|
||||
import { find } from 'vs/base/common/arrays';
|
||||
|
||||
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 _sessionName: string;
|
||||
private _viewTemplate: IProfilerViewTemplate;
|
||||
// mapping of event categories to what column they display under
|
||||
// used for coallescing multiple events with different names to the same column
|
||||
private _columnMapping: { [event: string]: string } = {};
|
||||
|
||||
private _onColumnsChanged = new Emitter<Slick.Column<Slick.SlickData>[]>();
|
||||
public onColumnsChanged: Event<Slick.Column<Slick.SlickData>[]> = this._onColumnsChanged.event;
|
||||
|
||||
private _filter: ProfilerFilter = { clauses: [] };
|
||||
|
||||
constructor(
|
||||
public connection: IConnectionProfile,
|
||||
@IProfilerService private _profilerService: IProfilerService,
|
||||
@INotificationService private _notificationService: INotificationService,
|
||||
@IDialogService private _dialogService: IDialogService
|
||||
) {
|
||||
super();
|
||||
this._state = new ProfilerState();
|
||||
// set inital state
|
||||
this.state.change({
|
||||
isConnected: false,
|
||||
isStopped: true,
|
||||
isPaused: false,
|
||||
isRunning: false,
|
||||
autoscroll: true
|
||||
});
|
||||
|
||||
this._profilerService.registerSession(uriPrefixes.connection + generateUuid(), connection, this).then((id) => {
|
||||
this._id = id;
|
||||
this.state.change({ isConnected: true });
|
||||
});
|
||||
let searchFn = (val: { [x: string]: string }, exp: string): Array<number> => {
|
||||
let ret = new Array<number>();
|
||||
for (let i = 0; i < this._columns.length; i++) {
|
||||
let colVal = val[this._columns[i]];
|
||||
if (colVal && colVal.toLocaleLowerCase().indexOf(exp.toLocaleLowerCase()) > -1) {
|
||||
ret.push(i);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
let filterFn = (data: Array<Slick.SlickData>): Array<Slick.SlickData> => {
|
||||
return FilterData(this._filter, data);
|
||||
};
|
||||
|
||||
this._data = new TableDataView<Slick.SlickData>(undefined, searchFn, undefined, filterFn);
|
||||
}
|
||||
|
||||
public get providerType(): string {
|
||||
return this.connection ? this.connection.providerName : undefined;
|
||||
}
|
||||
|
||||
public set viewTemplate(template: IProfilerViewTemplate) {
|
||||
this._data.clear();
|
||||
this._viewTemplate = template;
|
||||
|
||||
let newColumns = this._viewTemplate.columns.reduce<Array<string>>((p, e) => {
|
||||
p.push(e.name);
|
||||
return p;
|
||||
}, []);
|
||||
|
||||
let newMapping: { [event: string]: string } = {};
|
||||
this._viewTemplate.columns.forEach(c => {
|
||||
c.eventsMapped.forEach(e => {
|
||||
newMapping[e] = c.name;
|
||||
});
|
||||
});
|
||||
this.setColumnMapping(newColumns, newMapping);
|
||||
}
|
||||
|
||||
public get viewTemplate(): IProfilerViewTemplate {
|
||||
return this._viewTemplate;
|
||||
}
|
||||
|
||||
public set sessionName(name: string) {
|
||||
if (!this.state.isRunning || !this.state.isPaused) {
|
||||
this._sessionName = name;
|
||||
}
|
||||
}
|
||||
|
||||
public get sessionName(): string {
|
||||
return this._sessionName;
|
||||
}
|
||||
|
||||
public getTypeId(): string {
|
||||
return ProfilerInput.ID;
|
||||
}
|
||||
|
||||
public resolve(refresh?: boolean): Promise<IEditorModel> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public getName(): string {
|
||||
let name: string = nls.localize('profilerInput.profiler', "Profiler");
|
||||
if (!this.connection) {
|
||||
return name;
|
||||
}
|
||||
name += ': ' + this.connection.serverName.substring(0, 20);
|
||||
return name;
|
||||
}
|
||||
|
||||
public getResource(): URI {
|
||||
return URI.from({
|
||||
scheme: ProfilerInput.SCHEMA,
|
||||
path: '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 setColumnMapping(columns: Array<string>, mapping: { [event: string]: string }) {
|
||||
this._columns = columns;
|
||||
this._columnMapping = mapping;
|
||||
this._onColumnsChanged.fire(this.columns);
|
||||
}
|
||||
|
||||
public get connectionName(): string {
|
||||
if (!types.isUndefinedOrNull(this.connection)) {
|
||||
if (this.connection.databaseName) {
|
||||
return `${this.connection.serverName} ${this.connection.databaseName}`;
|
||||
} else {
|
||||
return `${this.connection.serverName}`;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return nls.localize('profilerInput.notConnected', "Not connected");
|
||||
}
|
||||
}
|
||||
|
||||
public get id(): ProfilerSessionID {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public get state(): ProfilerState {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
public get filter(): ProfilerFilter {
|
||||
return this._filter;
|
||||
}
|
||||
|
||||
public onSessionStopped(notification: azdata.ProfilerSessionStoppedParams) {
|
||||
this._notificationService.error(nls.localize("profiler.sessionStopped", "XEvent Profiler Session stopped unexpectedly on the server {0}.", this.connection.serverName));
|
||||
|
||||
this.state.change({
|
||||
isStopped: true,
|
||||
isPaused: false,
|
||||
isRunning: false
|
||||
});
|
||||
}
|
||||
|
||||
public onProfilerSessionCreated(params: azdata.ProfilerSessionCreatedParams) {
|
||||
if (types.isUndefinedOrNull(params.sessionName) || types.isUndefinedOrNull(params.templateName)) {
|
||||
this._notificationService.error(nls.localize("profiler.sessionCreationError", "Error while starting new session"));
|
||||
} else {
|
||||
this._sessionName = params.sessionName;
|
||||
let sessionTemplate = find(this._profilerService.getSessionTemplates(), (template) => {
|
||||
return template.name === params.templateName;
|
||||
});
|
||||
if (!types.isUndefinedOrNull(sessionTemplate)) {
|
||||
let newView = find(this._profilerService.getViewTemplates(), (view) => {
|
||||
return view.name === sessionTemplate.defaultView;
|
||||
});
|
||||
if (!types.isUndefinedOrNull(newView)) {
|
||||
this.viewTemplate = newView;
|
||||
}
|
||||
}
|
||||
|
||||
this.data.clear();
|
||||
this.state.change({
|
||||
isStopped: false,
|
||||
isPaused: false,
|
||||
isRunning: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public onSessionStateChanged(state: ProfilerState) {
|
||||
this.state.change(state);
|
||||
}
|
||||
|
||||
public onMoreRows(eventMessage: azdata.ProfilerSessionEvents) {
|
||||
if (eventMessage.eventsLost) {
|
||||
this._notificationService.warn(nls.localize("profiler.eventsLost", "The XEvent Profiler session for {0} has lost events.", this.connection.serverName));
|
||||
}
|
||||
|
||||
let newEvents = [];
|
||||
for (let i: number = 0; i < eventMessage.events.length && i < 500; ++i) {
|
||||
let e: azdata.ProfilerEvent = eventMessage.events[i];
|
||||
let data = {};
|
||||
data['EventClass'] = e.name;
|
||||
data['StartTime'] = e.timestamp;
|
||||
|
||||
// Using ' ' instead of '' fixed the error where clicking through events
|
||||
// with empty text fields causes future text panes to be highlighted.
|
||||
// This is a temporary fix
|
||||
data['TextData'] = ' ';
|
||||
for (let key in e.values) {
|
||||
let columnName = this._columnMapping[key];
|
||||
if (columnName) {
|
||||
let value = e.values[key];
|
||||
data[columnName] = value;
|
||||
}
|
||||
}
|
||||
newEvents.push(data);
|
||||
}
|
||||
|
||||
if (newEvents.length > 0) {
|
||||
this._data.push(newEvents);
|
||||
}
|
||||
}
|
||||
|
||||
filterSession(filter: ProfilerFilter) {
|
||||
this._filter = filter;
|
||||
if (this._filter.clauses.length !== 0) {
|
||||
this.data.filter();
|
||||
} else {
|
||||
this.data.clearFilter();
|
||||
}
|
||||
}
|
||||
|
||||
clearFilter() {
|
||||
this._filter = { clauses: [] };
|
||||
this.data.clearFilter();
|
||||
}
|
||||
|
||||
confirmSave(): Promise<ConfirmResult> {
|
||||
if (this.state.isRunning || this.state.isPaused) {
|
||||
return this._dialogService.show(Severity.Warning,
|
||||
nls.localize('confirmStopProfilerSession', "Would you like to stop the running XEvent session?"),
|
||||
[
|
||||
nls.localize('profilerClosingActions.yes', "Yes"),
|
||||
nls.localize('profilerClosingActions.no', "No"),
|
||||
nls.localize('profilerClosingActions.cancel', "Cancel")
|
||||
]).then((selection: IShowResult) => {
|
||||
if (selection.choice === 0) {
|
||||
this._profilerService.stopSession(this.id);
|
||||
return ConfirmResult.DONT_SAVE;
|
||||
} else if (selection.choice === 1) {
|
||||
return ConfirmResult.DONT_SAVE;
|
||||
} else {
|
||||
return ConfirmResult.CANCEL;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve(ConfirmResult.DONT_SAVE);
|
||||
}
|
||||
}
|
||||
|
||||
isDirty(): boolean {
|
||||
return this.state.isRunning || this.state.isPaused;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
super.dispose();
|
||||
this._profilerService.disconnectSession(this.id);
|
||||
}
|
||||
}
|
||||
@@ -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 * as DOM from 'vs/base/browser/dom';
|
||||
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
|
||||
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 { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { StandaloneCodeEditor } from 'vs/editor/standalone/browser/standaloneCodeEditor';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
|
||||
class ProfilerResourceCodeEditor extends StandaloneCodeEditor {
|
||||
|
||||
// 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,
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
@IEditorService protected editorService: IEditorService,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService,
|
||||
@IHostService hostService: IHostService
|
||||
|
||||
) {
|
||||
super(ProfilerResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, hostService);
|
||||
}
|
||||
|
||||
public createEditorControl(parent: HTMLElement, configuration: IEditorOptions): editorCommon.IEditor {
|
||||
return this.instantiationService.createInstance(ProfilerResourceCodeEditor, parent, 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): Promise<void> {
|
||||
return super.setInput(input, options, CancellationToken.None)
|
||||
.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: DOM.Dimension) {
|
||||
this.getControl().layout(dimension);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 'sql/workbench/contrib/profiler/common/interfaces';
|
||||
import { ProfilerInput } from 'sql/workbench/contrib/profiler/browser/profilerInput';
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { attachTableStyler } from 'sql/platform/theme/common/styler';
|
||||
import { RowSelectionModel } from 'sql/base/browser/ui/table/plugins/rowSelectionModel.plugin';
|
||||
import { IProfilerStateChangedEvent } from 'sql/workbench/contrib/profiler/common/profilerState';
|
||||
import { FindWidget, ITableController, IConfigurationChangedEvent, ACTION_IDS, PROFILER_MAX_MATCHES } from 'sql/workbench/contrib/profiler/browser/profilerFindWidget';
|
||||
import { ProfilerFindNext, ProfilerFindPrevious } from 'sql/workbench/contrib/profiler/browser/profilerActions';
|
||||
|
||||
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/findState';
|
||||
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';
|
||||
import { Dimension } from 'vs/base/browser/dom';
|
||||
import { textFormatter, slickGridDataItemColumnValueExtractor } from 'sql/base/browser/ui/table/formatters';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar';
|
||||
import { localize } from 'vs/nls';
|
||||
import { CopyKeybind } from 'sql/base/browser/ui/table/plugins/copyKeybind.plugin';
|
||||
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
|
||||
import { handleCopyRequest } from 'sql/workbench/contrib/profiler/browser/profilerCopyHandler';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
|
||||
export interface ProfilerTableViewState {
|
||||
scrollTop: number;
|
||||
scrollLeft: number;
|
||||
}
|
||||
|
||||
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 _statusbarItem: IDisposable;
|
||||
private _showStatusBarItem: boolean;
|
||||
|
||||
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,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IStatusbarService private _statusbarService: IStatusbarService,
|
||||
@IClipboardService private _clipboardService: IClipboardService,
|
||||
@ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService
|
||||
) {
|
||||
super(ProfilerTableEditor.ID, telemetryService, _themeService, storageService);
|
||||
this._actionMap[ACTION_IDS.FIND_NEXT] = this._instantiationService.createInstance(ProfilerFindNext, this);
|
||||
this._actionMap[ACTION_IDS.FIND_PREVIOUS] = this._instantiationService.createInstance(ProfilerFindPrevious, this);
|
||||
this._showStatusBarItem = true;
|
||||
}
|
||||
|
||||
public createEditor(parent: HTMLElement): void {
|
||||
|
||||
this._overlay = document.createElement('div');
|
||||
this._overlay.className = 'overlayWidgets';
|
||||
this._overlay.style.width = '100%';
|
||||
this._overlay.style.zIndex = '4';
|
||||
parent.appendChild(this._overlay);
|
||||
|
||||
this._profilerTable = new Table(parent, {
|
||||
sorter: (args) => {
|
||||
let input = this.input as ProfilerInput;
|
||||
if (input && input.data) {
|
||||
input.data.sort(args);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
dataItemColumnValueExtractor: slickGridDataItemColumnValueExtractor
|
||||
});
|
||||
this._profilerTable.setSelectionModel(new RowSelectionModel());
|
||||
const copyKeybind = new CopyKeybind();
|
||||
copyKeybind.onCopy((e) => {
|
||||
// in context of this table, the selection mode is row selection, copy the whole row will get a lot of unwanted data
|
||||
// ignore the passed in range and create a range so that it only copies the currently selected cell value.
|
||||
const activeCell = this._profilerTable.activeCell;
|
||||
handleCopyRequest(this._clipboardService, this.textResourcePropertiesService, new Slick.Range(activeCell.row, activeCell.cell), (row, cell) => {
|
||||
const fieldName = this._input.columns[cell].field;
|
||||
return this._input.data.getItem(row)[fieldName];
|
||||
});
|
||||
});
|
||||
this._profilerTable.registerPlugin(copyKeybind);
|
||||
attachTableStyler(this._profilerTable, this._themeService);
|
||||
|
||||
this._findState = new FindReplaceState();
|
||||
this._findState.onFindReplaceStateChange(e => this._onFindStateChange(e));
|
||||
|
||||
this._finder = new FindWidget(
|
||||
this,
|
||||
this._findState,
|
||||
this._contextViewService,
|
||||
this._keybindingService,
|
||||
this._contextKeyService,
|
||||
this._themeService
|
||||
);
|
||||
}
|
||||
|
||||
public setInput(input: ProfilerInput): Promise<void> {
|
||||
this._showStatusBarItem = true;
|
||||
this._input = input;
|
||||
|
||||
this._updateRowCountStatus();
|
||||
|
||||
if (this._columnListener) {
|
||||
this._columnListener.dispose();
|
||||
}
|
||||
this._columnListener = input.onColumnsChanged(e => {
|
||||
this._profilerTable.columns = e.map(e => {
|
||||
e.formatter = textFormatter;
|
||||
return e;
|
||||
});
|
||||
this._profilerTable.autosizeColumns();
|
||||
});
|
||||
if (this._stateListener) {
|
||||
this._stateListener.dispose();
|
||||
}
|
||||
this._stateListener = input.state.onProfilerStateChange(e => this._onStateChange(e));
|
||||
input.data.onRowCountChange(() => {
|
||||
this._profilerTable.updateRowCount();
|
||||
this._updateRowCountStatus();
|
||||
});
|
||||
|
||||
input.data.onFilterStateChange(() => {
|
||||
this._profilerTable.grid.invalidateAllRows();
|
||||
this._profilerTable.updateRowCount();
|
||||
this._updateRowCountStatus();
|
||||
});
|
||||
|
||||
if (this._findCountChangeListener) {
|
||||
this._findCountChangeListener.dispose();
|
||||
}
|
||||
this._findCountChangeListener = input.data.onFindCountChange(() => this._updateFinderMatchState());
|
||||
|
||||
this._profilerTable.setData(input.data);
|
||||
this._profilerTable.columns = input.columns.map(c => {
|
||||
c.formatter = textFormatter;
|
||||
return c;
|
||||
});
|
||||
|
||||
this._profilerTable.autosizeColumns();
|
||||
this._input.data.currentFindPosition.then(val => {
|
||||
this._profilerTable.setActiveCell(val.row, val.col);
|
||||
this._updateFinderMatchState();
|
||||
}, er => { });
|
||||
|
||||
this._input.onDispose(() => {
|
||||
this._disposeStatusbarItem();
|
||||
});
|
||||
return Promise.resolve(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._profilerTable.autosizeColumns();
|
||||
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, PROFILER_MAX_MATCHES).then(p => {
|
||||
if (p) {
|
||||
this._profilerTable.setActiveCell(p.row, p.col);
|
||||
this._updateFinderMatchState();
|
||||
this._finder.focusFindInput();
|
||||
}
|
||||
});
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
private _updateRowCountStatus(): void {
|
||||
if (this._showStatusBarItem) {
|
||||
let message = this._input.data.filterEnabled ?
|
||||
localize('ProfilerTableEditor.eventCountFiltered', "Events (Filtered): {0}/{1}", this._input.data.getLength(), this._input.data.getLengthNonFiltered())
|
||||
: localize('ProfilerTableEditor.eventCount', "Events: {0}", this._input.data.getLength());
|
||||
|
||||
this._disposeStatusbarItem();
|
||||
this._statusbarItem = this._statusbarService.addEntry({ text: message }, 'status.eventCount', localize('status.eventCount', "Event Count"), StatusbarAlignment.RIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
private _disposeStatusbarItem() {
|
||||
if (this._statusbarItem) {
|
||||
this._statusbarItem.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public saveViewState(): ProfilerTableViewState {
|
||||
this._disposeStatusbarItem();
|
||||
this._showStatusBarItem = false;
|
||||
let viewElement = this._profilerTable.grid.getCanvasNode().parentElement;
|
||||
return {
|
||||
scrollTop: viewElement.scrollTop,
|
||||
scrollLeft: viewElement.scrollLeft
|
||||
};
|
||||
}
|
||||
|
||||
public restoreViewState(state: ProfilerTableViewState): void {
|
||||
this._showStatusBarItem = true;
|
||||
this._updateRowCountStatus();
|
||||
let viewElement = this._profilerTable.grid.getCanvasNode().parentElement;
|
||||
viewElement.scrollTop = state.scrollTop;
|
||||
viewElement.scrollLeft = state.scrollLeft;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user