From a46e202567a10618e3ec708ba87f464dd3693fb9 Mon Sep 17 00:00:00 2001 From: JasonYeMSFT Date: Thu, 21 Sep 2023 10:39:06 -0700 Subject: [PATCH] Show inline action when using password (#2178) * Show inline action when using password * Fix lint --- package.json | 12 ++++++++++ src/postgres/commands/checkAuthentication.ts | 3 ++- .../commands/registerPostgresCommands.ts | 7 ++++++ src/postgres/tree/ClientConfigFactory.ts | 10 ++++++-- src/postgres/tree/PostgresDatabaseTreeItem.ts | 23 +++++++++++++++---- src/postgres/tree/PostgresServerTreeItem.ts | 4 ++-- 6 files changed, 49 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 91c4465c4..0dfcedb11 100644 --- a/package.json +++ b/package.json @@ -434,6 +434,12 @@ "category": "PostgreSQL", "command": "postgreSQL.openStoredProcedure", "title": "Open Stored Procedure" + }, + { + "category": "PostgreSQL", + "command": "postgreSQL.showPasswordlessWiki", + "title": "Learn more about authenticating with Azure Active Directory", + "icon": "$(warning)" } ], "submenus": [ @@ -604,6 +610,11 @@ "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBGraphDatabase", "group": "1@1" }, + { + "command": "postgreSQL.showPasswordlessWiki", + "when": "view =~ /azure(ResourceGroups|azureFocusView)/ && viewItem =~ /postgresDatabase(?![a-z])/i && viewItem =~ /usesPassword/i", + "group": "inline" + }, { "command": "postgreSQL.createDatabase", "when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /postgresServer(?![a-z])/i", @@ -1027,6 +1038,7 @@ "clean": "git clean -dfx", "compile": "tsc -watch", "package": "vsce package --githubBranch main", + "package-local": "vsce package", "lint": "eslint --ext .ts .", "lint-fix": "eslint --ext .ts . --fix", "pretest": "npm run webpack-prod", diff --git a/src/postgres/commands/checkAuthentication.ts b/src/postgres/commands/checkAuthentication.ts index a72235e57..6799d060c 100644 --- a/src/postgres/commands/checkAuthentication.ts +++ b/src/postgres/commands/checkAuthentication.ts @@ -20,7 +20,8 @@ export async function checkAuthentication(context: IActionContext, treeItem: Pos continue; } try { - clientConfig = await PostgresClientConfigFactory.getClientConfigFromNode(treeItem.parent, treeItem.databaseName); + const getClientConfigResult = await PostgresClientConfigFactory.getClientConfigFromNode(treeItem.parent, treeItem.databaseName); + clientConfig = getClientConfigResult.clientConfig; } catch (error) { const parsedError: IParsedError = parseError(error); diff --git a/src/postgres/commands/registerPostgresCommands.ts b/src/postgres/commands/registerPostgresCommands.ts index 26fc5703a..e0a5b4c52 100644 --- a/src/postgres/commands/registerPostgresCommands.ts +++ b/src/postgres/commands/registerPostgresCommands.ts @@ -8,6 +8,7 @@ import { defaults } from "pg"; import { languages } from "vscode"; import { connectedPostgresKey, doubleClickDebounceDelay, postgresDefaultDatabase, postgresLanguageId } from "../../constants"; import { ext } from "../../extensionVariables"; +import { openUrl } from "../../utils/openUrl"; import { PostgresCodeLensProvider } from "../services/PostgresCodeLensProvider"; import { PostgresDatabaseTreeItem } from "../tree/PostgresDatabaseTreeItem"; import { configurePostgresFirewall } from "./configurePostgresFirewall"; @@ -50,6 +51,7 @@ export function registerPostgresCommands(): void { registerCommandWithTreeNodeUnwrapping('postgreSQL.createStoredProcedureQuery', createPostgresStoredProcedureQuery); registerCommandWithTreeNodeUnwrapping('postgreSQL.executeQuery', executePostgresQueryInDocument); registerCommandWithTreeNodeUnwrapping('postgreSQL.copyConnectionString', copyConnectionString); + registerCommandWithTreeNodeUnwrapping('postgreSQL.showPasswordlessWiki', showPasswordlessWiki); } export async function loadPersistedPostgresDatabase(): Promise { @@ -74,3 +76,8 @@ export async function loadPersistedPostgresDatabase(): Promise { } }); } + +export async function showPasswordlessWiki(): Promise { + // @todo: Create forward link + await openUrl("https://aka.ms/postgresql-passwordless-wiki"); +} diff --git a/src/postgres/tree/ClientConfigFactory.ts b/src/postgres/tree/ClientConfigFactory.ts index a08f313b5..f52b59713 100644 --- a/src/postgres/tree/ClientConfigFactory.ts +++ b/src/postgres/tree/ClientConfigFactory.ts @@ -17,7 +17,10 @@ export const postgresResourceType = "https://ossrdbms-aad.database.windows.net/" * Creates an object that can be used to execute a postgres query with connection test and telemetry. */ export class PostgresClientConfigFactory { - public static async getClientConfigFromNode(treeItem: PostgresServerTreeItem, databaseName: string): Promise { + public static async getClientConfigFromNode(treeItem: PostgresServerTreeItem, databaseName: string): Promise<{ + type: "azureAd" | "password" | "connectionString", + clientConfig: ClientConfig + }> { const parsedConnectionString = await treeItem.getFullConnectionString(); const azureUserSession = await getAzureAdUserSession(); @@ -54,7 +57,10 @@ export class PostgresClientConfigFactory { context.telemetry.properties.clientConfigType = clientConfigType; await testClientConfig(clientConfig); }); - return clientConfig; + return { + type: clientConfigType, + clientConfig + }; } catch (error) { // If the client config failed during test, skip and try the next available one. } diff --git a/src/postgres/tree/PostgresDatabaseTreeItem.ts b/src/postgres/tree/PostgresDatabaseTreeItem.ts index 7bc7e2baf..cf6a51cc7 100644 --- a/src/postgres/tree/PostgresDatabaseTreeItem.ts +++ b/src/postgres/tree/PostgresDatabaseTreeItem.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // eslint-disable-next-line import/no-internal-modules -import { AzExtParentTreeItem, AzExtTreeItem, GenericTreeItem, IActionContext, IParsedError, parseError, TreeItemIconPath } from '@microsoft/vscode-azext-utils'; -import { ClientConfig } from 'pg'; +import { AzExtParentTreeItem, AzExtTreeItem, createContextValue, GenericTreeItem, IActionContext, IParsedError, parseError, TreeItemIconPath } from '@microsoft/vscode-azext-utils'; import { ThemeIcon } from 'vscode'; import { ext } from '../../extensionVariables'; import { localize } from '../../utils/localize'; @@ -19,15 +18,17 @@ import { PostgresTablesTreeItem } from './PostgresTablesTreeItem'; export class PostgresDatabaseTreeItem extends AzExtParentTreeItem { public static contextValue: string = "postgresDatabase"; - public readonly contextValue: string = PostgresDatabaseTreeItem.contextValue; + public contextValue: string = PostgresDatabaseTreeItem.contextValue; public readonly childTypeLabel: string = "Resource Type"; public readonly databaseName: string; public readonly parent: PostgresServerTreeItem; public autoSelectInTreeItemPicker: boolean = true; + public isShowingPasswordWarning: boolean; constructor(parent: PostgresServerTreeItem, databaseName: string) { super(parent); this.databaseName = databaseName; + this.isShowingPasswordWarning = false; } public get label(): string { @@ -63,7 +64,10 @@ export class PostgresDatabaseTreeItem extends AzExtParentTreeItem { } try { - const clientConfig: ClientConfig = await PostgresClientConfigFactory.getClientConfigFromNode(this.parent, this.databaseName); + const { type, clientConfig } = await PostgresClientConfigFactory.getClientConfigFromNode(this.parent, this.databaseName); + if (type === "password") { + void this.showPasswordWarning(context); + } const children: AzExtTreeItem[] = [ new PostgresFunctionsTreeItem(this, clientConfig), new PostgresTablesTreeItem(this, clientConfig) @@ -93,7 +97,16 @@ export class PostgresDatabaseTreeItem extends AzExtParentTreeItem { } public async deleteTreeItemImpl(): Promise { - const clientConfig = await PostgresClientConfigFactory.getClientConfigFromNode(this.parent, this.databaseName); + const { clientConfig } = await PostgresClientConfigFactory.getClientConfigFromNode(this.parent, this.databaseName); await runPostgresQuery(clientConfig, `Drop Database ${wrapArgInQuotes(this.databaseName)};`); } + + private async showPasswordWarning(context: IActionContext): Promise { + if (this.isShowingPasswordWarning) { + return; + } + this.isShowingPasswordWarning = true; + this.contextValue = createContextValue([PostgresDatabaseTreeItem.contextValue, "usesPassword"]); + await this.refresh(context); + } } diff --git a/src/postgres/tree/PostgresServerTreeItem.ts b/src/postgres/tree/PostgresServerTreeItem.ts index 9a4aa9f13..20e7dbe76 100644 --- a/src/postgres/tree/PostgresServerTreeItem.ts +++ b/src/postgres/tree/PostgresServerTreeItem.ts @@ -105,7 +105,7 @@ export class PostgresServerTreeItem extends AzExtParentTreeItem { } else if (this.partialConnectionString.databaseName) { dbNames = [this.partialConnectionString.databaseName]; } else { - const clientConfig = await PostgresClientConfigFactory.getClientConfigFromNode(this, postgresDefaultDatabase); + const { clientConfig } = await PostgresClientConfigFactory.getClientConfigFromNode(this, postgresDefaultDatabase); const query = `SELECT datname FROM pg_catalog.pg_database WHERE datistemplate = false;`; const queryResult = await runPostgresQuery(clientConfig, query); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return @@ -146,7 +146,7 @@ export class PostgresServerTreeItem extends AzExtParentTreeItem { stepName: 'createPostgresDatabase', validateInput: (name: string) => validateDatabaseName(name, getChildrenTask) }); - const clientConfig = await PostgresClientConfigFactory.getClientConfigFromNode(this, databaseName); + const { clientConfig } = await PostgresClientConfigFactory.getClientConfigFromNode(this, databaseName); context.showCreatingTreeItem(databaseName); await runPostgresQuery(clientConfig, `Create Database ${wrapArgInQuotes(databaseName)};`); return new PostgresDatabaseTreeItem(this, databaseName);