More Layering (#9139)

* move handling generated files to the serilization classes

* remove unneeded methods

* add more folders to strictire compile, add more strict compile options

* update ci

* wip

* add more layering and fix issues

* add more strictness

* remove unnecessary assertion

* add missing checks

* fix indentation

* wip

* remove jsdoc

* fix layering

* fix compile

* fix compile errors

* wip

* wip

* finish layering

* fix css

* more layering

* rip

* reworking results serializer

* move some files around

* move capabilities to platform wip

* implement capabilities register provider

* fix capabilities service

* fix usage of the regist4ry

* add contribution

* wip

* wip

* wip

* remove no longer good parts

* fix strict-nulls

* fix issues with startup

* another try

* fix startup

* fix imports

* fix tests

* fix tests

* fix more tests

* fix tests

* fix more tests

* fix broken test

* fix tabbing

* fix naming

* wip

* finished layering

* fix imports

* fix valid layers

* fix layers
This commit is contained in:
Anthony Dresser
2020-02-15 01:54:23 -06:00
committed by GitHub
parent 873c6a39fe
commit 506c6a5e5f
338 changed files with 815 additions and 724 deletions

View File

@@ -0,0 +1,173 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { EditorInput, EditorModel } from 'vs/workbench/common/editor';
import { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
export class DashboardInput extends EditorInput {
private _uri: string;
public static ID: string = 'workbench.editorinputs.connectiondashboardinputs';
public static SCHEMA: string = 'sqldashboard';
private _initializedPromise: Thenable<void>;
private _onConnectionChanged: IDisposable;
public get initializedPromise(): Thenable<void> {
return this._initializedPromise;
}
private _uniqueSelector: string;
public hasBootstrapped = false;
// Holds the HTML content for the editor when the editor discards this input and loads another
private _parentContainer: HTMLElement;
constructor(
_connectionProfile: IConnectionProfile,
@IConnectionManagementService private _connectionService: IConnectionManagementService,
@IModeService modeService: IModeService,
@IModelService model: IModelService
) {
super();
// TODO; possible refactor
// basically this is mimicing creating a "model" (the backing model for text for editors)
// for dashboard, even though there is no backing text. We need this so that we can
// tell the icon theme services that we are a dashboard resource, therefore loading the correct icon
// vscode has a comment that Mode's will eventually be removed (not sure the state of this comment)
// so this might be able to be undone when that happens
if (!model.getModel(this.getResource())) {
model.createModel('', modeService.create('dashboard'), this.getResource());
}
this._initializedPromise = _connectionService.connectIfNotConnected(_connectionProfile, 'dashboard').then(
u => {
this._uri = u;
const info = this._connectionService.getConnectionInfo(u);
if (info) {
this._onConnectionChanged = this._connectionService.onConnectionChanged(e => {
if (e.connectionUri === u) {
this._onDidChangeLabel.fire();
}
});
}
}
);
}
public setUniqueSelector(uniqueSelector: string): void {
this._uniqueSelector = uniqueSelector;
}
public getTypeId(): string {
return DashboardInput.ID;
}
public getResource(): URI {
return URI.from({
scheme: 'dashboard',
path: 'dashboard'
});
}
public getName(): string {
if (!this.connectionProfile) {
return '';
}
let name = this.connectionProfile.connectionName ? this.connectionProfile.connectionName : this.connectionProfile.serverName;
if (this.connectionProfile.databaseName
&& !this.isMasterMssql()) {
// Only add DB name if this is a non-default, non-master connection
name = name + ':' + this.connectionProfile.databaseName;
}
return name;
}
private isMasterMssql(): boolean {
return this.connectionProfile.providerName === mssqlProviderName
&& this.connectionProfile.databaseName.toLowerCase() === 'master';
}
public get uri(): string {
return this._uri;
}
public dispose(): void {
this._disposeContainer();
if (this._onConnectionChanged) {
this._onConnectionChanged.dispose();
}
this._connectionService.disconnect(this._uri);
super.dispose();
}
private _disposeContainer() {
if (!this._parentContainer) {
return;
}
const parentNode = this._parentContainer.parentNode;
if (parentNode) {
parentNode.removeChild(this._parentContainer);
this._parentContainer = null;
}
}
set container(container: HTMLElement) {
this._disposeContainer();
this._parentContainer = container;
}
get container(): HTMLElement {
return this._parentContainer;
}
public supportsSplitEditor(): boolean {
return false;
}
public get connectionProfile(): IConnectionProfile {
return this._connectionService.getConnectionProfile(this._uri);
}
public resolve(refresh?: boolean): Promise<EditorModel> {
return undefined;
}
public get hasInitialized(): boolean {
return !!this._uniqueSelector;
}
public get uniqueSelector(): string {
return this._uniqueSelector;
}
public matches(otherinput: any): boolean {
return otherinput instanceof DashboardInput
&& DashboardInput.profileMatches(this.connectionProfile, otherinput.connectionProfile);
}
// similar to the default profile match but without databasename
public static profileMatches(profile1: IConnectionProfile, profile2: IConnectionProfile): boolean {
return profile1 && profile2
&& profile1.providerName === profile2.providerName
&& profile1.serverName === profile2.serverName
&& profile1.userName === profile2.userName
&& profile1.authenticationType === profile2.authenticationType
&& profile1.groupFullName === profile2.groupFullName;
}
public get tabColor(): string {
return this._connectionService.getTabColorForUri(this.uri);
}
}

View File

@@ -0,0 +1,290 @@
/*---------------------------------------------------------------------------------------------
* 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/common/editor/profiler/profilerState';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import * as azdata from 'azdata';
import * as nls from 'vs/nls';
import { EditorInput } 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 * as types from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
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
) {
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();
}
isDirty(): boolean {
return this.state.isRunning || this.state.isPaused;
}
dispose() {
super.dispose();
this._profilerService.disconnectSession(this.id);
}
}