Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Command line interface commands and connection management service to prompt for provider installation #25056

Merged
merged 6 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/envi
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';

//#region decorators
enum Command {
connect = 'connect',
openConnectionDialog = 'openConnectionDialog'
}

type PathHandler = (uri: URI) => Promise<boolean>;

type PathHandler = (args: NativeParsedArgs) => Promise<boolean>;
const pathMappings: { [key: string]: PathHandler } = {};

interface PathHandlerOptions {
Expand Down Expand Up @@ -98,13 +101,13 @@ export class CommandLineWorkbenchContribution implements IWorkbenchContribution,
public async processCommandLine(args: NativeParsedArgs): Promise<void> {
let profile: IConnectionProfile = undefined;
let commandName = undefined;

if (args) {
if (this._commandService) {
commandName = args.command;
}

if (args.server) {
profile = this.readProfileFromArgs(args);
profile = await this.readProfileFromArgs(args);
}
}
let showConnectDialogOnStartup: boolean = this._configurationService.getValue('workbench.showConnectDialogOnStartup');
Expand All @@ -120,16 +123,6 @@ export class CommandLineWorkbenchContribution implements IWorkbenchContribution,
}
let connectedContext: azdata.ConnectedContext = undefined;
if (profile) {
if (profile.providerName && !this._capabilitiesService.providers[profile.providerName]) {
const installed = await this._connectionManagementService.handleUnsupportedProvider(profile.providerName);
if (!installed) {
// User cancelled install prompt so exit early since we won't be able to connect
return;
}
// Recreate the profile here now that we have our provider registered so that we get the correct
// option names. See https://github.com/microsoft/azuredatastudio/issues/20773 for details about the issue
Charles-Gagnon marked this conversation as resolved.
Show resolved Hide resolved
profile = this.readProfileFromArgs(args);
}
if (this._notificationService) {
this._notificationService.status(localize('connectingLabel', "Connecting: {0}", profile.serverName), { hideAfter: 2500 });
}
Expand All @@ -143,11 +136,18 @@ export class CommandLineWorkbenchContribution implements IWorkbenchContribution,
this.logService.warn('Failed to connect due to error: ' + getErrorMessage(err));
}
}

if (commandName) {
if (this._notificationService) {
this._notificationService.status(localize('runningCommandLabel', "Running command: {0}", commandName), { hideAfter: 2500 });
}
await this._commandService.executeCommand(commandName, connectedContext);
if (commandName === Command.connect || commandName === Command.openConnectionDialog) {
// Run handlers for 'connect' and 'openConnectionDialog' commands.
await this.runCommandHandler(commandName, args);
} else {
// Execute other commands via commandService.
await this._commandService.executeCommand(commandName, connectedContext);
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
}
} else if (connectedContext) {
// If we were given a file and it was opened with the sql editor,
// we want to connect the given profile to to it.
Expand All @@ -171,53 +171,71 @@ export class CommandLineWorkbenchContribution implements IWorkbenchContribution,
}
}

/**
* Handles user provided URL to initiate command handler for supported commands.
* @param uri User provided URL in format: azuredatastudio://{command}?{option1}={value1}&{option2}={value2}...
* @returns True if URL was opened successfully, false otherwise
* @throws Error if invalid URL handler used.
*/
public async handleURL(uri: URI): Promise<boolean> {
let key = uri.authority;
let args = this.parseProtocolArgs(uri);

const result = await this.runCommandHandler(key, args);
if (typeof result !== 'boolean') {
throw new Error('Invalid URL Handler used in commandLine code.');
}

return result;
}

private async runCommandHandler(key: string, args: NativeParsedArgs) {
let method = pathMappings[key];

if (!method) {
return false;
}
method = method.bind(this);
const result = await method(uri);

if (typeof result !== 'boolean') {
throw new Error('Invalid URL Handler used in commandLine code.');
}

return result;
return await method(args);
}

@pathHandler({
path: 'connect'
path: Command.connect
})
public async handleConnect(uri: URI): Promise<boolean> {
public async handleConnect(args: NativeParsedArgs): Promise<boolean> {
try {
let args = this.parseProtocolArgs(uri);
if (!args.server) {
this._notificationService.warn(localize('warnServerRequired', "Cannot connect as no server information was provided"));
this._notificationService?.warn(localize('warnServerRequired', "Cannot connect as no server information was provided"));
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
return true;
}
let isOpenOk = await this.confirmConnect(args);
if (isOpenOk) {
await this.processCommandLine(args);
const connectionProfile = await this.readProfileFromArgs(args);
try {
await this._connectionManagementService.connect(connectionProfile, undefined, {
saveTheConnection: true,
showDashboard: true,
showConnectionDialogOnError: true,
showFirewallRuleOnError: true
});
} catch (err) {
this.logService.warn('Failed to connect due to error: ' + getErrorMessage(err));
}
}
} catch (err) {
this._notificationService.error(localize('errConnectUrl', "Could not open URL due to error {0}", getErrorMessage(err)));
this._notificationService?.error(localize('errConnectUrl', "Could not open URL due to error {0}", getErrorMessage(err)));
}
// Handled either way
return true;
}

@pathHandler({
path: 'openConnectionDialog'
path: Command.openConnectionDialog
})
public async handleOpenConnectionDialog(uri: URI): Promise<boolean> {
public async handleOpenConnectionDialog(args: NativeParsedArgs): Promise<boolean> {
try {
let args = this.parseProtocolArgs(uri);
if (!args.server) {
this._notificationService.warn(localize('warnServerRequired', "Cannot connect as no server information was provided"));
this._notificationService?.warn(localize('warnServerRequired', "Cannot connect as no server information was provided"));
return true;
}
let isOpenOk = await this.confirmConnect(args);
Expand All @@ -226,15 +244,15 @@ export class CommandLineWorkbenchContribution implements IWorkbenchContribution,
return true;
}

const connectionProfile = this.readProfileFromArgs(args);
const connectionProfile = await this.readProfileFromArgs(args);
await this._connectionManagementService.showConnectionDialog(undefined, {
saveTheConnection: true,
showDashboard: true,
showConnectionDialogOnError: true,
showFirewallRuleOnError: true
}, connectionProfile);
} catch (err) {
this._notificationService.error(localize('errConnectUrl', "Could not open URL due to error {0}", getErrorMessage(err)));
this._notificationService?.error(localize('errConnectUrl', "Could not open URL due to error {0}", getErrorMessage(err)));
}
return true;
}
Expand Down Expand Up @@ -279,10 +297,20 @@ export class CommandLineWorkbenchContribution implements IWorkbenchContribution,
}
}

private readProfileFromArgs(args: NativeParsedArgs) {
private async readProfileFromArgs(args: NativeParsedArgs): Promise<IConnectionProfile | undefined> {
// Handle unsupported provider first thing before setting default provider.
if (args.provider && !this._capabilitiesService.providers[args.provider]) {
const installed = await this._connectionManagementService.handleUnsupportedProvider(args.provider);
if (!installed) {
// User cancelled install prompt so exit early since we won't be able to connect
return undefined;
}
}

let profile = new ConnectionProfile(this._capabilitiesService, null);
// We want connection store to use any matching password it finds
profile.savePassword = true;

profile.providerName = args.provider ?? Constants.mssqlProviderName;
profile.serverName = args.server;
profile.databaseName = args.database ?? '';
Expand Down
Loading