Adding Execution Plan Editor to ADS (#18696)

* Pushing Execution Plan Editor

* Renaming class
Handling error

* Awaiting for handlers to be registered

* Addressing some PR comments

* Fixing return type for provider

* Fixing editor id and removing unnecessary overrides

* Adding a namespace

* adding execution plan namespace

* Adding protocol comment

* Fixing if logic

* Fixing error message

* Cleaning up code

* cleanup code

* Adding help comments

* Fixing method call

* Using path.ts to get the base file name

* converting to lambda functions

* Adding comment for run action

* Fixing pr comments

* Fixing editor label

* Fixing doc comments

* Adding some more comments

* Fixign branding in comments
This commit is contained in:
Aasim Khan
2022-03-16 15:07:29 -07:00
committed by GitHub
parent 95980130c8
commit a0c2dc199e
24 changed files with 692 additions and 161 deletions

View File

@@ -0,0 +1,131 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import type * as azdata from 'azdata';
import { IExecutionPlanService } from 'sql/workbench/services/executionPlan/common/interfaces';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { Event, Emitter } from 'vs/base/common/event';
interface ExecutionPlanProviderRegisteredEvent {
id: string,
provider: azdata.executionPlan.ExecutionPlanProvider
}
export class ExecutionPlanService implements IExecutionPlanService {
private _providers: { [handle: string]: azdata.executionPlan.ExecutionPlanProvider; } = Object.create(null);
private _onProviderRegister: Emitter<ExecutionPlanProviderRegisteredEvent> = new Emitter<ExecutionPlanProviderRegisteredEvent>();
private _providerRegisterEvent: Event<ExecutionPlanProviderRegisteredEvent>;
constructor(
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService,
@IQuickInputService private _quickInputService: IQuickInputService,
@IExtensionService private _extensionService: IExtensionService
) {
this._providerRegisterEvent = this._onProviderRegister.event;
}
/**
* Runs the actions using the provider that supports the fileFormat provided.
* @param fileFormat fileformat of the underlying execution plan file. It is used to get the provider that support it.
* @param action executionPlanService action to be performed.
*/
private async _runAction<T>(fileFormat: string, action: (handler: azdata.executionPlan.ExecutionPlanProvider) => Thenable<T>): Promise<T> {
let providers = Object.keys(this._capabilitiesService.providers);
if (!providers) {
providers = await new Promise(resolve => {
this._capabilitiesService.onCapabilitiesRegistered(e => {
resolve(Object.keys(this._capabilitiesService.providers));
});
});
}
let epProviders: string[] = [];
for (let i = 0; i < providers.length; i++) {
const providerCapabilities = this._capabilitiesService.getCapabilities(providers[i]);
if (providerCapabilities.connection.supportedExecutionPlanFileExtensions?.includes(fileFormat)) {
epProviders.push(providers[i]);
}
}
let selectedProvider: string;
/**
* This handles the case when multiple providers support the same execution plan extension.
* The code shows a quick pick and lets user select the provider they want to open the execution plan file with.
*/
if (epProviders.length > 1) {
const providerQuickPick = this._quickInputService.createQuickPick<IQuickPickItem>();
providerQuickPick.items = epProviders.map(p => {
return {
label: p,
ariaLabel: p
};
});
providerQuickPick.placeholder = localize('selectExecutionPlanProvider', "Select a provider to open execution plan");
selectedProvider = await new Promise((resolve) => {
providerQuickPick.onDidChangeSelection(e => {
providerQuickPick.hide();
resolve(e[0].label);
});
providerQuickPick.show();
});
} else {
selectedProvider = epProviders[0];
}
if (!selectedProvider) {
return Promise.reject(new Error(localize('providerIdNotValidError', "Valid provider is required in order to interact with ExecutionPlanService")));
}
await this._extensionService.whenInstalledExtensionsRegistered();
let handler = this._providers[selectedProvider];
if (!handler) {
handler = await new Promise((resolve, reject) => {
this._providerRegisterEvent(e => {
if (e.id === selectedProvider) {
resolve(e.provider);
}
});
setTimeout(() => {
/**
* Handling a possible edge case where provider registered event
* might have been called before we await for it.
*/
resolve(this._providers[selectedProvider]);
}, 30000);
});
}
if (handler) {
return Promise.resolve(action(handler));
} else {
return Promise.reject(new Error(localize('noHandlerRegistered', "No valid execution plan handler is registered")));
}
}
registerProvider(providerId: string, provider: azdata.executionPlan.ExecutionPlanProvider): void {
if (this._providers[providerId]) {
throw new Error(`A execution plan provider with id "${providerId}" is already registered`);
}
this._providers[providerId] = provider;
this._onProviderRegister.fire({
id: providerId,
provider: provider
});
}
getExecutionPlan(planFile: azdata.executionPlan.ExecutionPlanGraphInfo): Promise<azdata.executionPlan.GetExecutionPlanResult> {
return this._runAction(planFile.graphFileType, (runner) => {
return runner.getExecutionPlan(planFile);
});
}
getSupportedExecutionPlanExtensionsForProvider(providerId: string): string[] | undefined {
return this._capabilitiesService.getCapabilities(providerId).connection.supportedExecutionPlanFileExtensions;
}
_serviceBrand: undefined;
}

View File

@@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type * as azdata from 'azdata';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const SERVICE_ID = 'executionPlanService';
export const IExecutionPlanService = createDecorator<IExecutionPlanService>(SERVICE_ID);
export interface IExecutionPlanService {
_serviceBrand: undefined;
/**
* Registers an execution plan service provider.
*/
registerProvider(providerId: string, provider: azdata.executionPlan.ExecutionPlanProvider): void;
/**
* Gets an execution plan for the given planFile.
*/
getExecutionPlan(planFile: azdata.executionPlan.ExecutionPlanGraphInfo): Promise<azdata.executionPlan.GetExecutionPlanResult>;
/**
* Get execution plan file extensions supported by the provider.
*/
getSupportedExecutionPlanExtensionsForProvider(providerId: string): string[];
}