From a34d3bb00014bc5a57e72f064e6ce4ef1346d776 Mon Sep 17 00:00:00 2001 From: jawelton74 <103591340+jawelton74@users.noreply.github.com> Date: Thu, 29 Sep 2022 15:36:00 -0700 Subject: [PATCH 1/3] Disable wild card index for Mongo 16MB documents (#1331) * Disable wild card index option if Mongo 16 MB document capability is present. * Formatted with prettier --- src/Explorer/Panes/AddCollectionPanel.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Explorer/Panes/AddCollectionPanel.tsx b/src/Explorer/Panes/AddCollectionPanel.tsx index bc750ab2e..dfaf5975c 100644 --- a/src/Explorer/Panes/AddCollectionPanel.tsx +++ b/src/Explorer/Panes/AddCollectionPanel.tsx @@ -122,7 +122,8 @@ export class AddCollectionPanel extends React.Component - {isCapabilityEnabled("EnableMongo") && ( + {isCapabilityEnabled("EnableMongo") && !isCapabilityEnabled("EnableMongo16MBDocumentSupport") && ( From 7c77ffda6c8938982610c59bdd7fd1a0b4dabf74 Mon Sep 17 00:00:00 2001 From: sunghyunkang1111 <114709653+sunghyunkang1111@users.noreply.github.com> Date: Tue, 4 Oct 2022 10:50:47 -0500 Subject: [PATCH 2/3] Add password reset callout (#1332) * Add password reset callout * Add create password text * Add password reset callout --- src/Contracts/ExplorerContracts.ts | 1 + src/Explorer/SplashScreen/SplashScreen.tsx | 80 +++++++++++++++++----- src/UserContext.ts | 7 +- src/hooks/usePostgres.ts | 4 ++ 4 files changed, 73 insertions(+), 19 deletions(-) diff --git a/src/Contracts/ExplorerContracts.ts b/src/Contracts/ExplorerContracts.ts index 7fd127bec..c04db6cbd 100644 --- a/src/Contracts/ExplorerContracts.ts +++ b/src/Contracts/ExplorerContracts.ts @@ -35,6 +35,7 @@ export enum MessageTypes { RefreshDatabaseAccount, CloseTab, OpenQuickstartBlade, + OpenPostgreSQLPasswordReset, } export { Versions, ActionContracts, Diagnostics }; diff --git a/src/Explorer/SplashScreen/SplashScreen.tsx b/src/Explorer/SplashScreen/SplashScreen.tsx index f99473fb0..b9c1286b2 100644 --- a/src/Explorer/SplashScreen/SplashScreen.tsx +++ b/src/Explorer/SplashScreen/SplashScreen.tsx @@ -11,6 +11,8 @@ import { TeachingBubbleContent, Text, } from "@fluentui/react"; +import { sendMessage } from "Common/MessageHandler"; +import { MessageTypes } from "Contracts/ExplorerContracts"; import { TerminalKind } from "Contracts/ViewModels"; import { useCarousel } from "hooks/useCarousel"; import { usePostgres } from "hooks/usePostgres"; @@ -91,6 +93,12 @@ export class SplashScreen extends React.Component { () => this.setState({}), (state) => state.showPostgreTeachingBubble ), + }, + { + dispose: usePostgres.subscribe( + () => this.setState({}), + (state) => state.showResetPasswordBubble + ), } ); } @@ -118,26 +126,36 @@ export class SplashScreen extends React.Component { : "Globally distributed, multi-model database service for any scale"}
- {userContext.apiType === "Postgres" && usePostgres.getState().showPostgreTeachingBubble && ( - usePostgres.getState().setShowPostgreTeachingBubble(false)} - primaryButtonProps={{ - text: "Get started", - onClick: () => { - useTabs.getState().openAndActivateReactTab(ReactTabKind.Quickstart); - usePostgres.getState().setShowPostgreTeachingBubble(false); - }, - }} - > - Welcome! If you are new to Cosmos DB PGSQL and need help with getting started, here is where you can - find sample data, query. - - )} + {userContext.apiType === "Postgres" && + usePostgres.getState().showPostgreTeachingBubble && + !usePostgres.getState().showResetPasswordBubble && ( + usePostgres.getState().setShowPostgreTeachingBubble(false)} + calloutProps={{ + directionalHint: DirectionalHint.rightCenter, + directionalHintFixed: true, + preventDismissOnLostFocus: true, + preventDismissOnResize: true, + preventDismissOnScroll: true, + }} + primaryButtonProps={{ + text: "Get started", + onClick: () => { + useTabs.getState().openAndActivateReactTab(ReactTabKind.Quickstart); + usePostgres.getState().setShowPostgreTeachingBubble(false); + }, + }} + > + Welcome! If you are new to Cosmos DB PGSQL and need help with getting started, here is where you + can find sample data, query. + + )} {mainItems.map((item) => ( {
))} + {userContext.apiType === "Postgres" && usePostgres.getState().showResetPasswordBubble && ( + usePostgres.getState().setShowResetPasswordBubble(false)} + calloutProps={{ + directionalHint: DirectionalHint.bottomRightEdge, + directionalHintFixed: true, + preventDismissOnLostFocus: true, + preventDismissOnResize: true, + preventDismissOnScroll: true, + }} + primaryButtonProps={{ + text: "Create", + onClick: () => { + sendMessage({ + type: MessageTypes.OpenQuickstartBlade, + }); + usePostgres.getState().setShowResetPasswordBubble(false); + }, + }} + > + This password will be used to connect to the database. + + )} {useCarousel.getState().showCoachMark && ( ): void { if (newContext.apiType === "Postgres") { usePostgres.getState().setShowPostgreTeachingBubble(true); localStorage.setItem(newContext.databaseAccount.id, "true"); - } else if (userContext.isTryCosmosDBSubscription || isNewAccount) { + } + if (userContext.isTryCosmosDBSubscription || isNewAccount) { useCarousel.getState().setShouldOpen(true); + usePostgres.getState().setShowResetPasswordBubble(true); localStorage.setItem(newContext.databaseAccount.id, "true"); traceOpen(Action.OpenCarousel); } @@ -130,6 +132,9 @@ function apiType(account: DatabaseAccount | undefined): ApiType { if (account.kind === "MongoDB" || account.kind === "Parse") { return "Mongo"; } + if (account.kind === "Postgres") { + return "Postgres"; + } return "SQL"; } diff --git a/src/hooks/usePostgres.ts b/src/hooks/usePostgres.ts index 222d163e7..5ab8c9dd3 100644 --- a/src/hooks/usePostgres.ts +++ b/src/hooks/usePostgres.ts @@ -2,10 +2,14 @@ import create, { UseStore } from "zustand"; interface TeachingBubbleState { showPostgreTeachingBubble: boolean; + showResetPasswordBubble: boolean; setShowPostgreTeachingBubble: (showPostgreTeachingBubble: boolean) => void; + setShowResetPasswordBubble: (showResetPasswordBubble: boolean) => void; } export const usePostgres: UseStore = create((set) => ({ showPostgreTeachingBubble: false, + showResetPasswordBubble: false, setShowPostgreTeachingBubble: (showPostgreTeachingBubble: boolean) => set({ showPostgreTeachingBubble }), + setShowResetPasswordBubble: (showResetPasswordBubble: boolean) => set({ showResetPasswordBubble }), })); From 81dfd761982f806c7d08ed41088ce952bc92fdd4 Mon Sep 17 00:00:00 2001 From: victor-meng <56978073+victor-meng@users.noreply.github.com> Date: Wed, 5 Oct 2022 17:32:05 -0700 Subject: [PATCH 3/3] Implement connection string tab for postgres (#1334) --- src/Contracts/ViewModels.ts | 2 + src/Explorer/Tabs/PostgresConnectTab.tsx | 133 +++++++++++++++++++++++ src/Explorer/Tabs/Tabs.tsx | 4 +- src/UserContext.ts | 15 +++ src/hooks/useKnockoutExplorer.ts | 16 ++- 5 files changed, 168 insertions(+), 2 deletions(-) create mode 100644 src/Explorer/Tabs/PostgresConnectTab.tsx diff --git a/src/Contracts/ViewModels.ts b/src/Contracts/ViewModels.ts index b6913c22d..fbfbec69c 100644 --- a/src/Contracts/ViewModels.ts +++ b/src/Contracts/ViewModels.ts @@ -397,6 +397,8 @@ export interface DataExplorerInputsFrame { dataExplorerVersion?: string; defaultCollectionThroughput?: CollectionCreationDefaults; isPostgresAccount?: boolean; + // TODO: Update this param in the OSS extension to remove isFreeTier, isMarlinServerGroup, and make nodes a flat array instead of an nested array + connectionStringParams?: any; flights?: readonly string[]; features?: { [key: string]: string; diff --git a/src/Explorer/Tabs/PostgresConnectTab.tsx b/src/Explorer/Tabs/PostgresConnectTab.tsx new file mode 100644 index 000000000..3487f7ee3 --- /dev/null +++ b/src/Explorer/Tabs/PostgresConnectTab.tsx @@ -0,0 +1,133 @@ +import { + Checkbox, + Dropdown, + Icon, + IconButton, + IDropdownOption, + ITextFieldStyles, + Label, + Link, + Stack, + Text, + TextField, + TooltipHost, +} from "@fluentui/react"; +import React from "react"; +import { userContext } from "UserContext"; + +export const PostgresConnectTab: React.FC = (): JSX.Element => { + const { adminLogin, nodes, enablePublicIpAccess } = userContext.postgresConnectionStrParams; + const [usePgBouncerPort, setUsePgBouncerPort] = React.useState(false); + const [selectedNode, setSelectedNode] = React.useState(nodes?.[0]?.value); + const portNumber = usePgBouncerPort ? "6432" : "5432"; + + const onCopyBtnClicked = (selector: string): void => { + const textfield: HTMLInputElement = document.querySelector(selector); + textfield.select(); + document.execCommand("copy"); + }; + + const textfieldStyles: Partial = { + root: { width: "100%" }, + field: { backgroundColor: "rgb(230, 230, 230)" }, + fieldGroup: { borderColor: "rgb(138, 136, 134)" }, + subComponentStyles: { label: { fontWeight: 400 } }, + description: { fontWeight: 400 }, + }; + + const nodesDropdownOptions: IDropdownOption[] = nodes.map((node) => ({ + key: node.value, + text: node.text, + })); + + const postgresSQLConnectionURL = `postgres://${adminLogin}:{your_password}@${selectedNode}:${portNumber}/citus?sslmode=require`; + const psql = `psql "host=${selectedNode} port=${portNumber} dbname=citus user=${adminLogin} password={your_password} sslmode=require"`; + const jdbc = `jdbc:postgresql://${selectedNode}:${portNumber}/citus?user=${adminLogin}&password={your_password}&sslmode=require`; + const libpq = `host=${selectedNode} port=${portNumber} dbname=citus user=${adminLogin} password={your_password} sslmode=require`; + const adoDotNet = `Server=${selectedNode};Database=citus;Port=${portNumber};User Id=${adminLogin};Password={your_password};Ssl Mode=Require;`; + + return ( +
+ + + + + + + + + + + { + const selectedNode = option.key as string; + setSelectedNode(selectedNode); + if (!selectedNode.startsWith("c.")) { + setUsePgBouncerPort(false); + } + }} + style={{ width: 200 }} + /> + + + + setUsePgBouncerPort(checked)} + disabled={!selectedNode?.startsWith("c.")} + /> + + + + onCopyBtnClicked("#postgresSQLConnectionURL")} /> + + + + + onCopyBtnClicked("#psql")} /> + + + + + onCopyBtnClicked("#JDBC")} /> + + + + + onCopyBtnClicked("#libpq")} /> + + + + onCopyBtnClicked("#adoDotNet")} /> + + + + + Only secure connections are supported. For production use cases, we recommend using the 'verify-full' + mode to enforce TLS certificate verification. You will need to download the Hyperscale (Citus) certificate, and + provide it when connecting to the database.{" "} + + Learn more + + +
+ ); +}; diff --git a/src/Explorer/Tabs/Tabs.tsx b/src/Explorer/Tabs/Tabs.tsx index 7bdb0c68c..c64b6ed56 100644 --- a/src/Explorer/Tabs/Tabs.tsx +++ b/src/Explorer/Tabs/Tabs.tsx @@ -2,10 +2,12 @@ import { CollectionTabKind } from "Contracts/ViewModels"; import Explorer from "Explorer/Explorer"; import { SplashScreen } from "Explorer/SplashScreen/SplashScreen"; import { ConnectTab } from "Explorer/Tabs/ConnectTab"; +import { PostgresConnectTab } from "Explorer/Tabs/PostgresConnectTab"; import { QuickstartTab } from "Explorer/Tabs/QuickstartTab"; import { useTeachingBubble } from "hooks/useTeachingBubble"; import ko from "knockout"; import React, { MutableRefObject, useEffect, useRef, useState } from "react"; +import { userContext } from "UserContext"; import loadingIcon from "../../../images/circular_loader_black_16x16.gif"; import errorIcon from "../../../images/close-black.svg"; import { useObservable } from "../../hooks/useObservable"; @@ -189,7 +191,7 @@ const onKeyPressReactTab = (e: KeyboardEvent, tabKind: ReactTabKind): void => { const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): JSX.Element => { switch (activeReactTab) { case ReactTabKind.Connect: - return ; + return userContext.apiType === "Postgres" ? : ; case ReactTabKind.Home: return ; case ReactTabKind.Quickstart: diff --git a/src/UserContext.ts b/src/UserContext.ts index 4fcb19295..29548e3f9 100644 --- a/src/UserContext.ts +++ b/src/UserContext.ts @@ -26,6 +26,20 @@ export interface CollectionCreationDefaults { throughput: ThroughputDefaults; } +export interface Node { + text: string; + value: string; + ariaLabel: string; +} + +export interface PostgresConnectionStrParams { + adminLogin: string; + enablePublicIpAccess: boolean; + nodes: Node[]; + isMarlinServerGroup: boolean; + isFreeTier: boolean; +} + interface UserContext { readonly authType?: AuthType; readonly masterKey?: string; @@ -52,6 +66,7 @@ interface UserContext { collectionId: string; partitionKey?: string; }; + readonly postgresConnectionStrParams?: PostgresConnectionStrParams; collectionCreationDefaults: CollectionCreationDefaults; } diff --git a/src/hooks/useKnockoutExplorer.ts b/src/hooks/useKnockoutExplorer.ts index e93d28196..1a26813d8 100644 --- a/src/hooks/useKnockoutExplorer.ts +++ b/src/hooks/useKnockoutExplorer.ts @@ -28,7 +28,7 @@ import { } from "../Platform/Hosted/HostedUtils"; import { CollectionCreation } from "../Shared/Constants"; import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility"; -import { PortalEnv, updateUserContext, userContext } from "../UserContext"; +import { Node, PortalEnv, updateUserContext, userContext } from "../UserContext"; import { listKeys } from "../Utils/arm/generatedClients/cosmos/databaseAccounts"; import { DatabaseAccountListKeysResult } from "../Utils/arm/generatedClients/cosmos/types"; import { getMsalInstance } from "../Utils/AuthorizationUtils"; @@ -357,6 +357,20 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) { if (inputs.isPostgresAccount) { updateUserContext({ apiType: "Postgres" }); + + if (inputs.connectionStringParams) { + // TODO: Remove after the nodes param has been updated to be a flat array in the OSS extension + let flattenedNodesArray: Node[] = []; + inputs.connectionStringParams.nodes?.forEach((node: Node | Node[]) => { + if (Array.isArray(node)) { + flattenedNodesArray = [...flattenedNodesArray, ...node]; + } else { + flattenedNodesArray.push(node); + } + }); + inputs.connectionStringParams.nodes = flattenedNodesArray; + updateUserContext({ postgresConnectionStrParams: inputs.connectionStringParams }); + } } if (inputs.features) {